Linux系统级IO②:RIO-带缓冲区IO实现

本文介绍了RIO(Robust I/O)库在UNIX系统中用于处理IO操作的一种方法,特别是针对read函数可能出现的不足值问题。RIO通过用户级缓冲区提高效率,减少系统调用。rio_readn和rio_writen函数实现了无缓冲区的读写操作,而rio_read则在内部管理缓冲区,避免了不足值问题。此外,还展示了rio_readlineb和rio_readnb如何利用RIO缓冲区进行行读取和指定数量的字符读取。实验演示了如何使用RIO读取标准输入并写入文件。RIO的主要优势在于减少了系统调用次数和提高了读取效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UNXI系统级的IO函数,在某些情况下,传送的字节数比用户要求的少,会出现不足值(short count)主要原因为:

  • ①读取时遇到EOF:文件的大小不足以填充read需要读取的字节数,那么返回不足值0表示提前遇到EOF
  • ②从终端读取文本行:read函数每次传送一个文本行,返回的不足值表示文本行的大小
  • ③读写网络SOCKET:由于网络延迟等原因,对Linux套接字执行read可能返回不足值(数据还没有接受完)

一种策略是反复调用read函数,直到将所需要的字节全部读取完毕。另一种思路采取缓冲区方式:

带缓冲区输入函数:允许应用程序高效的从文件中读取文本和二进制数据,避免频繁读取和以及解决不足问题。

RIO robust IO健壮IO读取包,其通过实现一个用户级的缓冲区来处理不足值,以及减少频繁的系统级IO的调用。

【rio_t】

一个用户级的内存缓冲区与一个打开的文件描述符关联:

#define RIO_BUFFERSIZE 8192
typedef struct {
    int rio_fd;   //关联的描述符fd
    int rio_cnt;  //剩余可读字节数
    char* rio_bufptr; //当前读开始位置
    char rio_buf[RIO_BUFFERSIZE];
}rio_t;

//初始化缓冲区  关联一个文件描述符fd
void rio_readinitb(rio_t* rp,int fd)  //初始化一个rio_t结构的读缓冲区
{

    rp->rio_bufptr=rp->rio_buf;//初始化读指针为 buf起点
    rp->rio_cnt=0;  //未读字节数
    rp->rio_fd=fd;
}

【rio_readn rio_writen】

实现无缓冲区的读写,主要应用于写入文件:

ssize_t rio_readn(int fd,void* usrbuf,size_t n)
{
    size_t nleft=n;
    ssize_t nread;
    char* bufp=(char*)usrbuf;
    while(nleft>0)
    {
        if((nread=read(fd,bufp,nleft))<0)
        {
            if(errno==EINTR)
                nread=0;
            else
                return -1;
        }
        else if(nread==0)
            break;
        nleft-=nread;  //将fd的数据直接传送到用户区
        bufp+=nread;

    }
}
ssize_t rio_writen(int fd,void* usrbuf,size_t n)
{
    size_t nleft=n;
    ssize_t nwritten;
    char* bufp=(char*)usrbuf;
    while(nleft>0)
    {
        if((nwritten=write(fd,bufp,nleft))<=0)
        {
            if(errno==EINTR)
                nwritten=0;
            else
                return -1;
        }
        nleft-=nwritten;
        bufp+=nwritten;  //指针右移
    }

}

核心函数rio_read

ssize_t rio_read(rio_t* rp,char* usrbuf,size_t n)
  • rp:需要读取的缓冲区(与一个打开文件描述符关联)
  • usrbuf:读取到的用户内存位置
  • n:读取的字节数

其基本实现思路为:

 

static ssize_t rio_read(rio_t* rp,char* usrbuf,size_t n)
{
    int cnt;
    while(rp->rio_cnt<=0)
    {
        //当缓冲区可读数量为0时,调用read读取到缓冲区
        rp->rio_cnt=read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf));
        if(rp->rio_cnt<0)
        {
            if(errno!=EINTR)
                return -1;  //读取发生错误
        }
        else if(rp->rio_cnt==0)
            return 0;
        else
            rp->rio_bufptr=rp->rio_buf;//缓冲区填充成功 重置起始指针
    }
    cnt =n;
    if(rp->rio_cnt<n)
    {
        cnt=rp->rio_cnt;//可读的字节数 取较小值
    }
    memcpy(usrbuf,rp->rio_bufptr,cnt);
    rp->rio_bufptr+=cnt;
    rp->rio_cnt-=cnt;//可读字节数减少
    return cnt;//返回读取成功的字节数
}

 

由此可见RIO_READ和Linux系统级的Read函数具有类似的含义。出错时,返回-1.在遇到EOF时,返回不足值0 。

 

基于rio_read的带缓冲区实现,可以进一步实现:

ssize_t rio_readlineb(rio_t *rp,void* usrbuf,sszie_t maxlen)

该函数读取一行的数据,它会调研rio_read函数从rio缓冲区一个个读取字符,可能发生两种情况:

  • 提前遇到\n 换行符,那么返回已经读取字节数的串
  • 读取到maxlen-1字节的字符,末尾添加NULL 返回这一行
//带缓冲区版本的readline实现
ssize_t rio_readlineb(rio_t* rp,void* usrbuf,size_t maxlen)
{
    int n;
    int rc;
    char c;
    char *bufp=(char*)usrbuf;
    for(n=1;n<maxlen;n++)  //填充maxlen-1个字符
    {
        if((rc=rio_read(rp,&c,1))==1) //每次从缓冲区中读取一个字符
        {
            *bufp++=c;
            if(c=='\n')
            {
                //遇到换行符 提前结束
                n++;
                break;
            }

        }
        else if(rc==0)
        {
            if(n==1)
                return 0;
            else
                break;
        } else
            return -1;
    }
    *bufp=0; //最后一个填充NULL
    return n-1;


}


而借助rio_read实现的rio_readnb实现缓冲区的读取n个字符:

ssize_t rio_readnb(rio_t* rp,void* usrbuf,size_t n)
{
    ssize_t nleft=n;
    ssize_t nread;
    char* bufp=(char*) usrbuf;
    while(nleft>0)
    {
        if((nread=rio_read(rp,bufp,nleft))<0)
        {
            return -1;
        }
        else if(nread==0)
            break;
        nleft-=nread;
        bufp+=nread;
    }
    return (n-nleft);

}

 

【实验与演示】

int main()
{
    int n;
    rio_t rio;
    char buf[1024];
    int file_fd=open("rio_test.txt",O_RDWR|O_CREAT,S_IWOTH);
    rio_readinitb(&rio,STDIN_FILENO);//初始化rio缓冲区 与  STDIN关联
    while((n=rio_readlineb(&rio,buf,1024))!=0)
        rio_writen(file_fd,buf,n);//无缓冲区 写入到STDOUT
}

通过关联标准的输入到用户级缓冲区RIO

不断的读取行,并通过直接复制的方式IO输出到 fd打开的文件中:

g++ rio_readlineb.cpp -o rio_readlineb

打开相应的文本文件(权限设定):

chmod 777 rio_test.txt

rio_test.txt 的内容: 

 

RIO是带有用户缓冲区的封装实现,相较于直接调用read函数的优势有以下几点:

①:当缓冲区有可读字节时,直接返回相应大小的字节,无需执行系统调用Read ,避免陷入内核

②:当缓冲区没有可读字节时,执行一次Read,并且Read一次申请读取缓冲区大小的字节,提高了存取效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值