1、非阻塞I/O

对低速设备的I/O操作可能会使进程永久阻塞,这类系统调用主要有如下情况:
(1)如果数据并不存在,则读文件可能会使调用者永远阻塞(例如读管道、终端设备和网络设备)。
(2)如果数据不能立即被接受,则写这些同样的文件也会使调用者永远阻塞;
(3)在某些条件发生之前,打开文件会被阻塞(例如以只写方式打开一个FIFO,那么在没有其他进程已用读方式打开该FIFO时);
(4)对已经加上强制性锁的文件进行读、写;
(5)某些ioctl操作;
(6)某些进程间通信函数;

非阻塞I/O调用open、read和write等I/O操作函数使上述的慢速系统调用在不能立即完成的情况下,立即出错返回。

对一个给定的描述符有两种方法设置其为非阻塞:
(1)如果是调用open以获得该描述符,则可指定O_NONBLOCK标志;
(2)对于已经打开的一个描述符,则可调用fcntl打开O_NONBLOCK文件状态标志(注意:设置文件状态标志的方法)。

写个非阻塞I/O的程序,程序功能是从标准输入读入100 000字节,并试图将它们写到标准输出上。

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/types.h>
6 #include <fcntl.h>
7
8 char buf[100000];
9 //将描述符设置为阻塞状态
10 void set_fl(int fd,int flags)
11 {
12 int val;
13 val = fcntl(fd,F_GETFL,0); //获取描述符
14 val |= flags; //添加状态
15 fcntl(fd,F_SETFL,val); //设置描述符
16 }
17 void clr_fl(int fd,int flags)
18 {
19 int val;
20 val = fcntl(fd,F_GETFL,0);
21 val &= ~flags; // 取消状态
22 fcntl(fd,F_SETFL,val);
23 }
24 int main()
25 {
26 int ntowrite,nwrite;
27 char *ptr;
28 ntowrite = read(STDIN_FILENO,buf,sizeof(buf));
29 fprintf(stderr,"read %d bytes\n",ntowrite);
30 set_fl(STDOUT_FILENO,O_NONBLOCK); //设置为阻塞状态
31 ptr = buf;
32 while(ntowrite > 0)
33 {
34 errno = 0;
35 nwrite = write(STDOUT_FILENO,ptr,ntowrite);
36 fprintf(stderr,"nwrite = %d,errno =%d\n",nwrite,errno);
37 if(nwrite > 0)
38 {
39 ptr += nwrite;
40 ntowrite -= nwrite;
41 }
42 }
43 clr_fl(STDOUT_FILENO,O_NONBLOCK); //设置为非阻塞状态
44 exit(0);
45 }

程序执行结果如下:若标准输出是普通文件,则可以期望write只执行一次。

若标识输出是终端,则期望write有时会返回小于100000的一个数字,有时会出错返回。按照下面要求执行,结果如图所示:

anker@killer-anker:~/Programs$ ./nonblock </lib/ld-linux.so.2 2> temp.file
anker@killer-anker:~/Programs$ cat temp.file

程序发出很多write操作,只有几个是正确输出数据的,其余的则出错返回。这种方式叫做轮询,在多用法系统上面浪费了CPU时间。

2、记录锁

  记录锁的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,实际上,由于内核没有“记录”的概念,因此,“记录锁”实际上是“区域锁”。

fcntl记录锁:int fcntl(int fd, int cmd, ……/* struct flock *flockptr */);对于记录锁,cmd是F_GETLK,f_SETLK,或F_SETLKW。第三个参数是一个指向flock结构的指针:

struct flock {
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}

  关于加锁和解锁区域需要注意以下几点:该区域可以在当前文件尾端处开始或越过其尾端处开始,但不能在文件起始位置之前或越过该起始位置;若l_len为0,则表示锁的区域从其起点开始,直至最大可能位置为止。

  两种锁:共享读锁(L_RDLCK)和独占写锁(L_WRLCK)的基本规则是,多个进程在一个给定的字节上可以有一把共享的读锁。但是在一个给定字节上的写锁则只能由一个进程独用。加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开的。

不同类型锁之间的兼容性如下:
读锁
写锁
无锁
允许
允许
一把或多把读锁
允许
拒绝
一把写锁
拒绝
拒绝
关于记录锁的自动继承和释放有三条规则:
(1)锁与进程、文件两方面有关:第一,当一个进程终止时,它锁建立的锁全部释放;第二,任何时候关闭一个描述符,则该进程通过这一描述符可以访问的文件上的任何一把锁都被释放。
(2)由fork产生的子进程不继承父进程所设置的锁。
(3)在执行exec后,新程序可以继承原执行程序的锁。
3、I/O多路转接
  其基本思想是:先构造一张有关描述符的表,然后调用一个函数,它要到这些描述符中的一个已准备好进行I/O时才返回。在返回时,它告诉进程哪一个描述符已准备好。select、pselect和poll函数能执行I/O多路转接。函数原型如下:
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);返回准备就绪的描述符数,超时则为0,出错为-1
参数timeout指定内核等待的时间,其结构如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
}
传给select的参数告诉内核:我们所关心的描述符;对于每个描述符我们所关心的条件;希望等待多长时间。
从select返回时,内核告诉我们:已准备好的描述符数量;哪一个描述符已准备好读、写或异常条件。

写个程序练习select函数,每隔5秒进行读取操作,程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/types.h>
6 #include <sys/select.h>
7
8 int main()
9 {
10 fd_set rfds;
11 struct timeval tv;
12 int retval;
13 char buf[1024];
14 for(;;)
15 {
16 FD_ZERO(&rfds);
17 FD_SET(STDIN_FILENO, &rfds);
18 /* Wait up to five seconds. */
19 tv.tv_sec = 5;
20 tv.tv_usec = 0;
21 retval = select(1, &rfds, NULL, NULL, &tv);
22 /* Don't rely on the value of tv now! */
23 if (retval)
24 {
25 printf("Data is available now.\n");
26 if(FD_ISSET(STDIN_FILENO, &rfds))
27 {
28 read(STDIN_FILENO,buf,1024);
29 printf("Read buf is: %s\n",buf);
30 }
31 }
32 else
33 printf("No data within five seconds.\n");
34 }
35 exit(0);
36 }

程序执行结果如下:

int pselect (int maxfdp1, fd_set *readset, fd_set * writeset, fd_set * exceptset, const struct timespec * timeout, const sigset_t *sigmask);
返回:准备好的描述字个数,0-超时,-1-出错。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
关于I/O多路转接将会在Unix网络编程中详细学习,在本章主要是了解这思想。
 4、异步I/O
  SVR4和4.3+BSD提供了使用一个信号(在SVR4中是SIGPOLL,在4.3+BSD中是SIGIO)的异步I/O方法,该信号通知进程,对某个描述符所关心的某个事件已经发生。异步I/O的一个限制是每个进程只有一个信号。
5、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);
参数:filedes     文件描述符
         iov         指向iovec结构数组的一个指针。
         iovcnt     数组元素的个数
返回值:若成功则返回已读、写的字节数,若出错则返回-1
struct iovec {
  void *iov_base; /* 起始地址 */
  size_t iov_len; /* 需要传输的字节数 */
};
readv() 系统调用从文件描述符 fd 关联的文件里读取数据到 iovcnt 个由 iov 结果描述的缓存区里。(分散读)

writev() 系统调用把 iovcnt 个由 iov 结构描述的缓存区数据写入文件描述符 fd 关联的文件里。(聚合写)

写个程序从标准输入读取数据存放到多个缓冲区中,然后从标准输出显示结果,程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <sys/uio.h>
6 #include <fcntl.h>
7 #include <string.h>
8
9 int main(int argc,char *argv[])
10 {
11 int fd1,fd2;
12 char *buf1 = malloc(10);
13 char *buf2 = malloc(1024);
14 struct iovec iov[2];
15 memset(buf1,0,11);
16 memset(buf2,0,1025);
17 ssize_t nwritten;
18 iov[0].iov_base = buf1;
19 iov[0].iov_len = 10;
20 iov[1].iov_base = buf2;
21 iov[1].iov_len = 1024;
22 readv(STDIN_FILENO,iov,2);
23 printf("call readv:\n");
24 printf("buf1 is: %s\tlength is: %d\n",buf1,strlen(buf1));
25 printf("buf2 is: %s\tlength is: %d\n",buf2,strlen(buf2));
26 printf("call writev:\n");
27 iov[0].iov_base = buf1;
28 iov[0].iov_len = strlen(buf1);
29 iov[1].iov_base = buf2;
30 iov[1].iov_len = strlen(buf2);
31 nwritten = writev(STDOUT_FILENO, iov, 2);
32 free(buf1);
33 free(buf2);
34 exit(0);
35 }

程序结果如下:

Unix环境高级编程(十五)高级I/O的更多相关文章

  1. Unix环境高级编程(十八)高级进程间通信

    本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符.服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信. 1 ...

  2. (十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  3. Unix环境高级编程(十二)线程控制

    本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互. 1.线程限制: Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询 ...

  4. Unix环境高级编程(十)信号续

    1.signal函数 Unix系统的信号机制最简单的接口是signal函数,函数原型如下: #include <signal.h> typedef void (*sighandler_t) ...

  5. Unix环境高级编程(十九)终端I/O

    终端I/O应用很广泛,用于终端.计算机之间的直接连线.调制解调器以及打印机等等.终端I/O有两种不同的工作模式: (1)规范模式输入处理:终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返 ...

  6. Unix环境高级编程(十六)进程间通信

    进程间通信(IPC)是指能在两个进程间进行数据交换的机制.现代OS都对进程有保护机制,因此两个进程不能直接交换数据,必须通过一定机制来完成. IPC的机制的作用: (1)一个软件也能更容易跟第三方软件 ...

  7. Unix环境高级编程(十四)守护进程实现时间服务器

    守护进程是在后台运行不受终端控制的进程(如输入.输出等),一般的网络服务都是以守护进程的方式运行.守护进程脱离终端的主要原因有两点:(1)用来启动守护进程的终端在启动守护进程之后,需要执行其他任务.( ...

  8. UNIX环境高级编程笔记之高级I/O

    本章说明了很多高级I/O功能: 非阻塞I/O——发一个I/O操作,不使其阻塞,记录锁,STREAMS机制 I/O多路转接——select和poll函数 readv和writev函数,以及存储映射I/O ...

  9. UNIX系统高级编程——第五章-标准I/O库-总结

    基础: 标准I/O库在ANSI C中定义,可移植在不同的系统 文件指针(FILE):标准I/O库操作的不是文件描述符,而是流.FILE文件指针包含的是维护流所需的信息 通过函数fileno获取流的文件 ...

随机推荐

  1. T-sql语句修改数据库逻辑名、数据库名、物理名

    --更改MSSQL数据库物理文件名Sql语句的写法 --注意:要在活动监视器里面确保没有进程连接你要改名的数据库!!!!!!!!!!!!!!!!!!!! -- Sql语句如下 USE master - ...

  2. Mac OS中Java Servlet与Http通信

    Java中一个Servlet其实就是一个类,用来扩展服务器的性能,服务器上驻留着可以通过“请求-响应”编程模型来访问的应用程序.Servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器 ...

  3. IOS info.plist配置文件

    创建ios程序时,系统会自动生成一个info.plist文件,它是一个必不可少的文件,因为在这个文件中,存放是应用程序的配置信息,比如本地化语言.版本号.软件名称等,当然,我们也可以在项目的属性中进行 ...

  4. python 斐波拉契数列数列

    '''斐波拉契数列'''def Fibonacci(n): first, next = 0, 1 i = 0; while i < n: print next first, next = nex ...

  5. ACM:图的BFS,走迷宫

    题目: 一个网格迷宫由n行m列的单元格组成,每一个单元格要么是空地(用1表示),要么是障碍物(用0来表示).你的任务是找一条从起点到终点的最短移动序列,当中UDLR分别表示往上.下.左.右移动到相邻单 ...

  6. RichTextBox 清空

    this.tbContent.Document.Blocks.Clear();

  7. wstring to wchar_t*

      If you want to convert from std::wstring to const WCHAR* (i.e. the returned pointer gives read-onl ...

  8. C++和.net的集合类对应

      Here's what I've found (ignoring the old non-generic collections): Array - C array, though the .NE ...

  9. CentOS(学习笔记一)

    菜鸟记录 一.配置网络.防火墙等 setup命令 二.查看网络 ifconfig 重启网络 /ect/init.d/network restart 或者ifup ethx(,,)等 查看网络配置文件 ...

  10. PHP中静态方法(static)与非静态方法的使用及区别

    今天再次学习这个内容,总是糊里糊涂的,想弄明白!!! static关键字用来修饰属性.方法,称这些属性.方法为静态属性.静态方法.static关键字声明一个属性或方法是和类相关的,而不是和类的某个特定 ...