RIO——健壮的IO包

前言:

内核态(内核模式)和用户态(用户模式)是linux的一种机制,用于限制应用可以执行的指令和可访问的地址空间,这通过设置某个控制寄存器的位来实现。

进程处于用户模式下,它不允许发起I/O操作,所以它必须通过系统调用进入内核模式才能对文件进行读取。

从用户模式切换到内核模式,主要的开销是处理器要将返回地址(当前指令的下一条指令地址)和额外的处理器状态(寄存器)压入到栈中,这些数据到会被压到内核栈而不是用户栈。

另外,一个进程使用系统调用还隐含了一点——调用系统调用的进程可能会被抢占

当内核代表用户执行系统调用时,

若该系统调用被阻塞,该进程就会进入休眠,然后由内核选择一个就绪状态,当前优先级最高的进程运行。

另外,即使系统调用没有被阻塞,当系统调用结束,从内核态返回时,若在系统调用期间出现了一个优先级更高的进程,则该进程会抢占使用了系统调用的进程。

内核态返回会返回到优先级高的进程,而不是原本的进程。


一、Linux下基本的I/O介绍:

ssize_t read(int fd, void *buf, size_tcount);

ssize_t write(int fd, void *buf, size_tcount);

Linux中,read write 是基本的系统级I/O函数。当用户进程使用read write 读写linux的文件时,进程会从用户态进入内核态,通过I/O操作读取文件中的数据。这样每次读写都通过系统调用会增大系统的负担的。


二、介绍一款在《深入理解计算机系统》中看到的RIO包:

RIO,全称Robust I/O,即健壮的IO包。它提供了与系统I/O类似的函数接口,在读取操作时,RIO包加入了读缓冲区,一定程度上增加了程序的读取效率。

首先有rio_t这个结构体,是一个读缓冲区的格式:

#define RIO_BUFSIZE     4096

typedefstruct

{

    int rio_fd;      //与缓冲区绑定的文件描述符的编号

    int rio_cnt;        //缓冲区中还未读取的字节数

    char *rio_bufptr;   //当前下一个未读取字符的地址

    char rio_buf[RIO_BUFSIZE];

}rio_t;

这个是rio的数据结构,通过rio_readinitb(rio_t *, int)可以将文件描述符与rio数据结构绑定起来。注意到这里的rio_buf的大小是4096,linux中文件的块大小。


初始化读缓冲区

void rio_readinitb(rio_t *rp, int fd)

{

   rp->rio_fd = fd;

   rp->rio_cnt = 0;

   rp->rio_bufptr = rp->rio_buf;

     return;

}

 

带缓冲区的读函数

static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)

{

    int cnt;

 

    while(rp->rio_cnt <= 0)    

    {

       rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));

       if(rp->rio_cnt < 0)

       {

           if(errno != EINTR)  //遇到中断类型错误的话应该进行读取,否则就返回错误

                return -1;

       }

        elseif(rp->rio_cnt == 0)   //读取到了EOF

           return0;

       else

           rp->rio_bufptr = rp->rio_buf;      //重置bufptr指针,令其指向第一个未读取字节,然后便退出循环

    }

 

   cnt = n;

    if((size_t)rp->rio_cnt < n)    

       cnt = rp->rio_cnt;

    memcpy(usrbuf, rp->rio_bufptr, n);

   rp->rio_bufptr += cnt;      //读取后需要更新指针

   rp->rio_cnt -= cnt;         //未读取字节也会减少

 

    return cnt;

}

 

 

ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)

{

   size_t leftcnt = n;

   ssize_t nread;

    char *buf = (char *)usrbuf;

 

    while(leftcnt > 0)

    {

       if((nread = rio_read(rp, buf, n)) < 0)

       {

           if(errno == EINTR)      //其实这里可以不用判断EINTR,rio_read()中已经对其处理了

                nread = 0;

           else

                return -1;

       }

       leftcnt -= nread;

       buf += nread;

    }

 

    return n-leftcnt;

}

 

 

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

{

   size_t n;

    int rd;

    char c, *bufp = (char *)usrbuf;

 

    for(n=1; n<maxlen; n++)    //n代表已接收字符的数量

    {

       if((rd=rio_read(rp, &c, 1)) == 1)

       {

           *bufp++ = c;

           if(c == '\n')

                break;

       }

       elseif(rd == 0)        //没有接收到数据

       {

           if(n == 1)          //如果第一次循环就没接收到数据,则代表无数据可接收

                return0;

           else

                break;

       }

       else                   

           return -1;

    }

   *bufp = 0;

 

    return n;

}

 

以上三个的读函数都是带缓冲区的,但是下面这个rio_writen不需要写缓冲。

因为,比如说,我们在写一个http的请求报文,然后将这个报文写入了对应socket的文件描述符的缓冲区,假设缓冲区大小为8K,该请求报文大小为1K。那么,如果缓冲区被设置为被填满才会自动将其真正写入文件(而且一般也是这样做的),那就是说如果没有提供一个刷新缓冲区的函数手动刷新,我还需要额外发送7K的数据将缓冲区填满,这个请求报文才能真正被写入到socket当中。所以,一般带有缓冲区的函数库都会一个刷新缓冲区的函数,用于将在缓冲区的数据真正写入文件当中,即使缓冲区没有被填满,而这也是C标准库的做法。然而,如果一个程序员一不小心忘记在写入操作完成后手动刷新,那么该数据(请求报文)便一直驻留在缓冲区,而你的进程还在傻傻地等待响应。

 因此,下面那个函数的fd就是int型的。

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;

       }

       bufp += nwritten;

       nleft -= nwritten;

    }

 

    return n;

}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值