unix高级环境编程(第12章高级终端)

出处:http://blog.sina.com.cn/s/blog_5d9c20070100gd7a.html


1.非阻塞I /O:

系统调用的两种分类:低速系统调用和其他。低速系统调用可能产生阻塞。

非阻塞:例如open、read和write操作不能完成,则立即出错返回。

两种方法对给给定得描述符制定非阻塞I/O:

(1)如果是调用open以获得该描述符,则可指定O_NONBLOCK标志。

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

2. 复习3.13节的fcntl函数:

   #include <sys/types.h>

   #include <unistd.h>

   #include <fcntl.h>

   Int fcntl(int filedes,int cmd ,….);返回:成功则依赖于cmd,出错为-1

根据cmd的五种功能:

F_DUPFD 复制一个现存的描述符 返回一个尚未打开的描述符中大于或等于第三个参数中各值最小值的描述符,新描述符与filedes共享同一个文件表项。

F_GETFD/F_SETFD 得到/设置close-on-exec文件描述符的标志 前者:若此标记的FD_CLOEXEC位为0,则在执行exec函数后文件键不会关闭。后者:按第三个参数设置。

F_GETFL/F_SETFD 得到/设置文件状态标志

O_RDONLY 只读

O_WRONLY 只写

O_RDWR 读/写                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

O_APPEND 增加到文件尾

O_NONBLOCK 非阻塞方式

O_SYNC 等待写完成

O_ASYNC 异步I/O

后者:可以更改后4个标志。其他位得改变将不受影响。

F_GETOWN/F_SETOWN 得到/设置异步I/O有权 取得SIGIO喝SIGURG信号的进程ID或进程组ID。若无法建立锁定,则返回-1,错误代码为EACCES或EAGAIN(不支持)。

F_GETLK/F_SETLK/F_SETLKW 得到/设置记录锁。作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。

3. 记录锁(区域锁):

在一个进程读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。

fcntl记录锁:

   #include <sys/types.h>

   #include <unistd.h>

   #include <fcntl.h>

   int fcntl(int filedes,int cmd,…..);

cmd:

F_GETLK 测试能否建立一把锁,如果 存在一把锁阻止创建由flockptr指向的锁,则把现存得锁写到flockptr指向得结构中,如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr结构的其他信息保持不变。

F_SETLK

F_SETLKW(W表示wait)。

struct flock{

   short l_type; F_RDLCK (共享读锁)、F_WRLCK(独占性写锁) or F_UNLCK

   short l_start; offset in bytes,与l_whence 有关

   short l_whence; SEEK_SET, SEEK_CUR, or SEEK_END

   short l_len; 长度,以bytes为单位。0表示锁定到EOF

   short l_pid; 与F_GETLK一起返回

};

基本规则:

多线程在给定的字节上可以有一把共享的读锁,但写锁只能由一个进程独用。如果给定得字节上有了读锁,则不能再加写锁。同样,有写锁,则不能再加读锁。

在设置或释放文件上的一把锁时,系统按需组合或裂开相邻区。例如,若对字节0 ~ 9 9设置一把读锁,然后对字节0 ~ 4 9设置一把写锁,则有两个加锁区: 0 ~ 4 9字节(写锁)及5 0 ~ 9 9(读锁)。又如,若1 0 0 ~ 1 9 9字节是加锁的区,需解锁第1 5 0字节,则内核将维持两把锁,一把用于1 0 0 ~ 1 4 9字节,另一把用于1 5 1 ~ 1 9 9字节。

   锁得隐含继承喝释放:

(a)锁与进程、文件两方面有关。任何时候关闭一个描述符时(包括复制的描述符),则改进程通过这一描述符可以把文件上的所有锁释放。即执行以下四步:

   fd1=open(pathname,…);

   read_lock(fd1,…);

   fd2 = dup(fd1);

   close(fd2);

(b)由fork产生得子进程不继承父进程所设置得锁。

(c)执行exec后,新进程可以继承原执行程序的锁。

   建议性锁和强制性锁:

使用fcntl文件锁都是建议性锁,强制性锁是非POSIX标准的。如果要使用强制性锁,要是整个系统可以使用强制性锁,就得取药重新挂载文件系统,mount使用参数-O man打开强制性锁,如果想在单个文件中开启强制性锁则可以关闭改文件的组执行权限且打开该文件得set-GID权限位。

建议性锁:例如:vi中使用了建议性锁,可是这把锁并不能阻止其他用户使用另一个没有使用建议性锁得编辑器。

4. 流:

任何一个数得处理模块可以压入流。压入:因为每一新模块总是查到流首之下,而强以前得模块下压。(后进、先出)。

   流消息:

流得所有输入和输出都是基于消息。流首和用户进程使用read、write、getmsg、getpmsg、putmsg、putpmsg交换消息。在流首、各个处理模块喝设备驱动程序之间,消息可以顺流而下,也可以逆流而上。

在用户进程和流首之间,消息由下列几个部分组成:消息类型、可选择得控制信息以及可选择得数据。write、putmsg和putpmsg产生的不同消息类型。控制信息和数据存放在strbuf结构中:

struct strbuf{

   int maxlen;buffer的长度。

   int len; buffer当前的byte数。-1则说明没有控制信息或数据。

   char *buf; 指向buffer的指针。

};

在我们所使用的函数(read、write、putmsg、putpmsg、getmsg、getpmsg)中,只涉及到三种消息类型:

M_DATA    I/O的用户数据

M_PROTO   协议控制信息

M_PCPROTO 高优先级协议控制信息

流中的消息都有一个排队优先级:

高优先级消息(最高)、优先波段消息(优先波段为1~255)和普通消息(最低,优先波段为0)。

每个流模块都有两个输入队列:一个从上面模块接受消息,一个从下面模块接受消息。

5. putmsg和putpmsg函数:

#include <stropts.h>

   int putmsg(int filedes,const struct strbuf *ctlptr, const struct strbuf * dateptr ,int flag);

   int putpmsg(int filedes, const struct strbuf * ctlptr, const struct strbuf * dataptr, int band, int flag);

返回:成功为0,出错为-1。

对流使用write函数,等效于不带控制信息,flag为0的putmsg。

 

6. 流ioctl操作:

使用ioctl可对流执行29中不同的而操作。

(1)使用I_CANPUT来测试由第三个参数说明得优先级波段是否可写。例如检测描述符是否应用流设备:ioctl(fd, I_CANPUT, 0) 如果不等于-1,则是,否则为否。

(2)使用I_LIST将使系统返回所有流上所有模块的名字,包括最顶端得驱动程序。帝三个参数应该是指向str_list结构得指针:

struct str_list{

   int    sl_nmods; str_mlist元素的数目

   struct str_mlist   *sl_modlist;

};

struct str_mlist{

   char l_name[FMNAMESZ+1];

};

如果第三个参数是0,则该函数返回的是模块数,而不是模块名。

7. write至流设备:

write至流设备产生一个M_DATA消息。

8. 写方式:

可以用ioctl取得和设置一个流的写方式。如果将request设置为I_GWROPT,第三个参数为指向一个整型变量得指针,则该流的当前写方式就在改整型变量中返回。如果request设置为I_SWROPT,则将写方式设置成第三个整型参数。目前,只定义了两个写方式值:

SNDZERO  对管道和FIFO得0长写造成顺流传送一个0长得消息。按系统默认,0长写不发送消息。

SNDPIPE 在流上已出错后,若调用write或putmsg,则向调用进程发送SIGPIPE信号。

9. getmsg和getpmsg函数:

   #include <stropts.h>

   int getmsg(int filedes,struct strbuf * ctlptr, struct strbuf *dataptr, int *flagptr);

   int getpmsg(int filedes,struct strbuf * ctlptr, struct strbuf *dataptr, int *bandptr, int * flagptr);

返回:成功为非负,出错为-1.

注意: flagptr和bandptr在调用之前,指向的整型数据设置为所希望的消息类型,返回时,被设置为所读到得消息类型。

如果flagptr指向得整型数据为0,则getmsg返回流首读队列中得下一个消息。。。。。???

10. 读方式:

可以用ioctl取得和设置一个流的读方式。如果将request设置为I_GWROPT,第三个参数为指向一个整型变量得指针,则该流的当前读方式就在改整型变量中返回。如果request设置为I_SWROPT,则将读方式设置成第三个整型参数。定义了三种读方式:

RNORM 普通,字节流方式。默认方式。

RMSGN 消息不删除方式,某次读只读了消息的一部分,剩下得部分保留,以供下一个读。

RMSGD 消息删除方式。剩下的删除,不再使用。

11. I/O多路转接:

如果使用阻塞I/O读两个描述符,可能会长时间阻塞在一个描述符上。我们用另一个技术来解决这个问题。

一种例子:

    ---------à                   ---------à

终端用户         调制解调器拨号进程         调制解调器ß------à电话线

        ß---------                   ß---------

该程序读终端,将所得数据写到调制解调器上,同时读调制解调器,将所得数据写到终端上。如果对两个输入都使用阻塞read,有可能在一个输入上长期阻塞,而另一个输入得数据则被丢失。处理这种特殊问题得一种方法是:设置两个进程,每个进程处理一条数据通路。

     ---------à  调制解调器拨号(父进程)----à

终端用户                                       调制解调器ß------à电话线

        ß--------- 调制解调器拨号(子进程) ß---

但产生两个进程互相配合问题。另一个解决方式是使用非阻塞I/O读取数据。缺点是:浪费CPU。最后一种方法是异步I/O。思想是:进程告诉内核,当一个描述符已准备号可以进行I/O时,用一个信号通知它。缺点:(1)并非所有的系统都支持这种机制。(2)这种信号对每个进程只有一个。如果该信号对两个描述符都起作用,那在接到这个信号时进程无法判别是哪个描述符已准备好进行I/O。

最后一种方法就是I/O多路转接:思想:先构造一张有关描述符得表,然后调用一个函数,找到这些描述符中的一个已准备号进行I/O时返回。在返回时,告诉进程哪个描述符已准备好。

12. select函数:

可以执行I/O多路转接,

   #include <sys/types.h>

   #include <sys/time.h>

   #include <unistd.h>

   int select(int maxfdp1, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct tim *tvptr);返回:准备就绪得描述符数(三个描述符集中打开的位是表示已准备好得描述符位。),若超时为0,出错为-1.

struct tim{

   long tv_sec;

   long tv_usec;

};

tvptr:

NULL 永远等待。

tvptr->tv_sec == 0 && tvptr->tv_usec == 0,完全不等待。

tvptr->tv_sec != 0 && tvptr->tv_usec != 0,等待指定的时间。

中间三个参数是指向描述符集的指针。分别是:

readfds:   可写,其中的一个描述符read不会阻塞,则此描述符就绪

writefds:  可读,其中的一个描述符write不会阻塞,则此描述符就绪

exceptfds: 异常,(a)在忘了连接上到达制定波特率外得数据或(b)在处于数据包方式的伪终端上发生了某些条件。

对于fg_set数据类型的四个宏:

FD_ZERO(fd_set * fdset);

FD_SET(int fd, fd_set *fdset); turn on bit for fd in fdset

FD_CLR(int fd, fd_set *fdset); turn off bit for fd in fdset

FD_ISSET(int fd, fd_set* fdset); test bit for fd in fdset

maxfdp1=max fd plus 1 表示三个集中最大的描述符编号值加1。

 

 

13. poll函数:

与select一样,监视多个描述符,如果有一个描述符就绪,则返回,进而我们可以对这个描述符进行I/O操作。保证最早就绪得描述符得到最早处理。

(1)如果当前可读写,返回满足响应事件的文件描述符得个数。

(2)如果不可读写,则阻塞进程,并等待驱动程序唤醒,重新调用poll函数或超时返回。

   #include <stropts.h>

   #include <poll.h>

   int poll(struct pollfd fdarray[], unsigned long nfds, int timeout);返回:准备就绪的描述符数,若超时为0,出错为-1.

与select不同的是:poll没有为每个条件构造一个描述符集,而是构造一个pollfd结构数组。每个元素制定一个描述符编号和其所关心的条件。

struct pollfd{

   int fd;

   short events;  关心的事件

   short revents;  已发生的事件

};

 

头四行测试可读性,接着三行测试可写性,最后三行则是异常条件。最后三行是内核在返回时设置的,如果events字段没有制定这三个值,如果相应条件发生,则在revents中也返回它们。

timeout:

INFTIM  永远等待。

0        不等待。

>0       等待timeout毫秒。

14. 异步I/O:

SVR4和4.3+BSD使用一个信号(SVR4是SIGPOLL,4.3+BSD是SIGIO)通知进程,对某个描述符所关心的某种事件已发生。它们所支持得异步I/O的一个限制是每个进程只有一个信号,如果要对几个描述符进行异步I/O,那么在进程接收到该信号时并不知道这一信号对应于哪个描述符。

(1)SVR4:

先调用ioctl(第二个参数设为I_SETSIG,第三个参数为。。。)说明产生SIGPOLL信号的条件,然后为该信号建立一个处理函数。

第三个参数。。。:

 

(2)4.3+BSD

为了接收SIGIO,需:

(a)调用signal或sigaction为该信号建立一个处理函数

(b)用F_SETOWN调用fcntl来设置进程ID和进程组ID,它们将接收对于改描述符得信号。

(c)用F_SETFL调用fcntl设置O_ASYNC状态标志,使在该描述符上可以进行异步I/O。

15. readv、writev函数:

读、写多个非连续缓存。汇聚写,散列读

   #include <sys/types.h>

   #include <sys/uio.h>\

   ssize_t readv(int filedes, const struct iovec iov[], int iovcnt);

   sszie_t wirtev(int filedes, const struct iovec iov[], int iovcnt);

返回:已读、写的字节数,出错为-1.

struct iovec{

   void *iov_base;

   size_t iov_len;

};

如果有两个缓存,我们把它们连续写到一个文件中,有三种方法:

(1)调用write两次,一次写一个缓存

(2)分配一个足够大的缓存,把这两个缓存放到这个缓存中,调用write一次。

(3)调用wirtev一次。

16. readn、writen函数:

这两个函数读写制定得N字节数据。

对于终端、网络和SVR4的流有下列两种性质:

(1)一次read操作所返回的数据可能少于所要求得数据。

(2)一次write操作所返回的数据可能少于制定输出得字节数。例如,下游模块的流量限制。

   #include “ourhdr.h”

   ssize_t readn(int filedes, void * buff, size_t nbytes);

   ssize_t written(int filedes, void * buff ,size_t nbytes);

返回:已读、写字节数,出错为-1

17. 存储映射I/O:

使一个磁盘文件与存储空间中的一个缓存相映射。于是从缓存中取数据,相当于读文件中的相应字节。类似,写到缓存,相当于写入文件。因为内核直接对存储缓存做I/O操作,而在read/write方式,内核要对用户缓存和它自己的缓存之间进行复制,然后用其缓存做I/O。所以系统时间减少。

   #include <sys/types.h>

   #include <sys/mman.h>

   caddr_t mmap(caddr_t addr , size_t len , int proc, int flag, int filedes, offset_t off);返回:成功则为映射区的起始地址,出错为-1.

caddr_t定义为char *。addr一般设为0。

filedes指定要被映射的文件描述符。在映射之前,要打开该文件。

off表示文件的偏移量。

porc:

PROT_READ  区域可读

PROT_WRITE 可写

PROT_EXEC  可执行

PROT_NONE  可存取

flag:

MAP_FIXED   返回值必须等于addr。若未设置此标志,且addr为非0,则内核只把addr视为何处设置映射区的一种建议。一般不设置此标志

MAP_SHARED 必须指定本标志或下一个。存储操作相当于对该文件write

MAP_PRIVATE 对映射区的存储操作将导致创建一个该文件的一个副本。是对副本的操作,而不是原始文件。

与映射存储区相关的两个信号:

SIGSEGV 因权限不够,进程试图存取不能存取的存储区。

SIGBUS  存取已经不存在的区域。

   进程终止时,或调用munmap后,存取映射区将被自动去除。关闭文件描述符filedes并不会解除映射区。

   #include <sys/types.h>

   #include <sys/mman.h>

   int munmap(caddr_t addr ,size_t len); 返回:成功为0,出错为-1.


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值