(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; }