APUE学习笔记——第十四章 高级IO

1、非阻塞IO
低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
(1)如果某些文件类型(管道、终端设备)的数据并不存在,则读操作可能会使调用者永远阻塞
(2)如果数据不能立即被上述同样类型的文件接受,则写操作也会使调用者永远阻塞
(3)在某种条件发生之前,打开某些类型的文件会被阻塞
(4)对已经加上强制性记录锁的文件进行读、写
(5)某些ioctl操作
(6)某些进程间通信函数
非阻塞I/O使我们调用open、read和write等I/O操作,使这些操作不会永远阻塞,如果这种操作不能完成,则调用立即出错返回,
表示该操作如继续执行将阻塞。
对一个给定的描述符有两种方法设置其为非阻塞:
(1)如果是调用open以获得该描述符,则可指定O_NONBLOCK标志;

(2)对于已经打开的一个描述符,则可调用fcntl打开O_NONBLOCK文件状态标志

下面给出一个非阻塞IO实例,它从标准输入读取500000字节,并试图将它们输出到标准输出

 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <fcntl.h>
 
 char buf[500000];
 void set_fl(int fd,int flag){
     int val;
     val = fcntl(fd,F_GETFL,0);  
     val |= flag;
     fcntl(fd,F_SETFL,val); 
 }
 void clr_fl(int fd,int flag){
     int val;
     val = fcntl(fd,F_GETFL,0);
     val &= ~flag;
     fcntl(fd,F_SETFL,val);
 }
 int main(){
     int ntowrite,nwrite;
     char *ptr;
     ntowrite = read(STDIN_FILENO,buf,sizeof(buf));
     fprintf(stderr,"read %d bytes\n",ntowrite);
     set_fl(STDOUT_FILENO,O_NONBLOCK); //设置非阻塞状态
     ptr = buf;
     while(ntowrite > 0){
         errno = 0;
         nwrite = write(STDOUT_FILENO,ptr,ntowrite);
         fprintf(stderr,"nwrite = %d,errno =%d\n",nwrite,errno);
         if(nwrite > 0){
             ptr += nwrite;
             ntowrite -= nwrite;
         }
     }
     clr_fl(STDOUT_FILENO,O_NONBLOCK); //清除非阻塞状态
     exit(0);
 }
此处给出书上的运行结果

当输出到文件只写一次,而输出到标准输出则write好多次。

2、记录锁
记录锁的功能是;当一个进程正在读或者修改文件的某个部分时,它可以阻止其他进程修改同一文件区。记录锁更合理的命名应该是字节范围锁,因为它锁定的只是文件中的一个区域(也可能是整个文件)。
用fcntl函数来实现记录锁
int fcntl(int filedes,int cmd,/**struct flock *flockptr/)
对于记录锁,cmd是F_GETLK,F_SETLK,F_SETLKW。flock结构如下:
struct flock{
    short l_type; /* F_RDLCK, F_WRLCK, F_UNLCK*/
off_t l_start; /* offset in bytes, relative to l_whence */
short l_whence; /* SEEK_SET,SEEK_CUR,SEEK_END */
off_t l_len; /* length, in bytes; 0 means lock to EOF*/
pid_t l_pid; /* returned with F_GETLK*/
}
对flock结构说明:
锁的类型(l_type)F_RDLCK(共享读锁), F_WRLCK(独占性写锁), F_UNLCK(解锁)
要加锁或解锁区域的起始字节偏移量,分别由l_start和l_whence决定
区域的字节长度由l_len表示

上面的兼容性规则适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求。如果一个进程对一个文件区域已经有一把锁,后来
该进程又企图在同一文件区间再加一把锁,那么新锁将替换老锁。
加读锁时,该文件描述符必须是读打开;加写锁时,该描述符必须是写打开
关于记录锁的自动继承和释放有三条规则:
(1)锁与进程、文件两方面有关:第一,当一个进程终止时,它锁建立的锁全部释放;第二,任何时候关闭一个描述符,则该进程通过这一描述符可以访问的文件上的任何一把锁都被释放。
(2)由fork产生的子进程不继承父进程所设置的锁。
(3)在执行exec后,新程序可以继承原执行程序的锁。
(此部分内容较多,详细见书本)

3、IO多路转接
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞IO:
 while((ntowrite = read(fd_in,buf,sizeof(buf))) > 0){
        nwrite = write(fd_out,buf,ntowrite);
        printf("ntowrite = %d, nwrite = %d\n",ntowrite,nwrite);
 }这种形式的阻塞IO到处可见。
 但是如果必须从两个描述符读,仍旧使用阻塞IO,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却得不到及时的处理。
 比较好的处理方法是使用IO多路转接。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行IO时,该函数才返回。在返回时,它告诉进程哪些描述符已经准备好可以进行IO。poll、pselect和select这三个函数使我们能够执行IO 多路转接。
 在POSIX平台上,select函数使我们可以执行IO多路转接,传向select的参数告诉内核:
 关心的描述符、对于每个描述符所关心的状态、愿意等待多长时间。从select返回时,内核告诉我们:
 已准备好的描述符的数量;对于读、写或异常这三个状态中的每一个,哪些描述符已准备好

使用这些返回信息,就可以调用相应的IO函数(一般是read或write),并且确知该函数不会阻塞
#include <sys/select.h>
int select(int maxfdpl,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *tvptr)
最后一个参数,它指定愿意等待的时间:
struct timeval{
    long tv_sec; //seconds
long tv_usec;//microseconds
}
tvptr==NULL:永远等待
tvptr->tv_sec==0&&tvptr->tv_usec==0:完全不等待
tvptr->tv_sec!=0||tvptr->tv_usec!=0:等待指定的秒数和微秒数
中间三个参数readfds, writefds, exceptfds是指向描述符集的指针,它们描述了我们关心的可读、可写和处异常条件的各个描述符。这种描述符集存在一种叫fd_set的数据类型中(在头文件select.h中有定义)。具体做法每个描述符对应于数据结构fd_set所占用内存空间的一个位,如果第i位为0则表示值为i的描述符不包含在该集中,反之亦然。为了方便用户使用,系统提供了如下的四个宏进行操作。
void FD_ZERO(fd_set *fdset); //清空fdset中的所有位
void FD_SET(int fd, fd_set *fdset); //在fdset中打开fd所对应的位
void FD_CLR(int fd, fd_set *fdset); //在fdset中关闭fd所对应的位
int FD_ISSET(int fd, fd_set *fdset); //测试fd是否在fdset中
通常做法是,先定义一个描述符集
fd_set rset;
int fd;
必须使用FD_ZERO清除其所有位
FD_ZERO(&rset);
然后设置我们所关心的位
FD_SET(fd, &rset);
FD_SET(STDOUT_FILENO,&rset);
从select返回时,用FD_ISSET测试该集中的一个给定位是否仍旧设置
if( FD_ISSET(fd, &rset)){
...
}

int pselect(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
timespec结构以秒和纳秒表示超时值。
对于pselect可使用一可选择的信号屏蔽字。若sigmask为空,则pselect的运行状况和select相同。否则,sigmask指向一信号屏蔽字在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时恢复以前的信号屏蔽字。

poll函数类似select,它起源于System V
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

IO多路转接将在进程间通信详细介绍。

4、readv和writev函数

readv(散布读)和writev(聚集写)函数用于在一次函数调用中读、写多个非连续缓冲区。
#include <sys/uio.h>
ssize_t readv(int filedes,const struct iovec *iov, int iovcnt)
ssize_t writev(int filedes,const struct iovec *iov,int iovcnt)
struct ioven{
    void* iov_base;/* starting address of buffer*/
size_t iov_len;/* size of buffer */
};
iov数组中的元素数由iovcnt说明,其最大值受限于IOV_MAX


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>

int main(){
    char buf1[32],buf2[64];
    memset(buf1,0,sizeof(buf1));
    memset(buf2,0,sizeof(buf2));
    struct iovec iov[2];
    iov[0].iov_base = buf1;
    iov[0].iov_len = 31;
    iov[1].iov_base = buf2;
    iov[1].iov_len = 63;
    int fd = open("cp.file",O_RDONLY);
    size_t rd = readv(fd,iov,2);//这里完全是按照字节数来读的
    printf("read bytes = %d\n",rd);
    int i;
    for(i = 0 ; i < 2;  i ++){
        printf("%s\n",(char *)iov[i].iov_base);
        puts("=================================");
    }
    iov[0].iov_base = buf1;
    iov[0].iov_len = strlen(buf1);
    iov[1].iov_base = buf2;
    iov[1].iov_len = strlen(buf2);
    
    size_t wr = writev(1,iov,2);
    printf("write bytes = %d\n",wr);
    return 0;
}

5、readn和writen函数

这两个函数是读、写指定的N字节数据

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

ssize_t readn(int fd,void* ptr,size_t n){
    size_t nleft = n;
    ssize_t nread;
    while(nleft > 0){
        if((nread = read(fd,ptr,nleft)) < 0) {
            if(nleft == n) return -1;
            else break;
        }else if(nread == 0){break;}
        nleft -= nread;
        ptr += nread;//point address add
    }
    return n-nleft;
}
ssize_t writen(int fd,const void* ptr,size_t n){
    size_t nleft = n;
    ssize_t nwritten;
    while(nleft > 0){
        if((nwritten = write(fd,ptr,nleft)) < 0){
            if(nleft == n) return -1;
            else break;
        }else if(nwritten == 0) break;
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n-nleft;
}

int main(){
    ssize_t rd,wr;
    char buf[20];
    rd = readn(0,buf,10);
    printf("%d, %s\n",rd,buf);
    memset(buf,0,sizeof(buf));
    strcpy(buf,"I love you, linux");
    wr = writen(1,buf,10);
    printf("%d\n",wr);
    return 0;
 
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值