第二部分 消息传递
第4章 管道和FIFO
- 管道
- 所有unix都提供管道
- 由pipe函数创建,提供单向数据流
- 函数返回两个文件描述符,前者打开来读,后者打开来写
- fork调用后,在父子进程间实现单向通信
- shell管道也是使用的这个,把读出端复制到相应进程的标准输入,把写入端复制到相应进程的标准输出
- 如果需要双工通信,则需要创建两个管道
- 每条数据都要穿越
用户-内核
接口两次 waitpid
:子进程终止时,内核给父进程产生一个SIGCHLD信号,父进程默认行为是忽略。如果父进程没有调用waitpid
来获取子进程(僵尸进程)的终止状态而直接终止的话,子进程将成为托孤给init进程的孤儿进程,内核将向init进程发送另外一个SIGCHLD信号,init进程将随后取得该僵尸进程的终止状态- 全双工管道
- SVR4的pipe函数、许多内核都提供的socketpair函数
- popen函数
FILE *popen(const char *command, const char *type)
- PATH环境变量可用于定位command
- popen在调用进程和所指定的命令之间创建一个管道
- type参数如果为“r”,那么调用进程读comand参数的标准输出;反之同理
int pclose(FILE *stream)
- pclose函数关闭由popen创建的标准IO流,等待终止并返回终止状态
- FIFO
- 无法在无亲缘关系的两个进程间创建一个管道并将它用于IPC通道(不考虑描述符传递)
- 每个FIFO有一个路径名与之关联,故也称
有名管道(named pipe)
- 由 mkfifo 函数创建
int mkfifo(const char *pathname, mode_t mode);
mode是权限位 - 创建后还需要调用open,并提供读写模式
- 半双工
- 以读模式打开FIFO将阻塞,不正当的操作顺序将导致父子进程的死锁
- write的原子性
- 只能用在本地文件系统上,不能用在通过NFS安装的文件系统上
- 只能在单台主机上使用的IPC形式
- 只发送请求不读取响应,可进行拒绝服务型攻击
- 超时时钟
- 子进程限制
- 内核为管道和FIFO维护一个访问计数器,存储访问同一个管道或FIFO的打开着描述符的格式。有了计数器,客户或服务器就能成功unlink,只要计数器不为0,内核就不会删除引用的管道或FIFO。而其他形式的IPC则不存在这样的计数器。
- 管道和FIFO的额外属性
- 设置成非阻塞
- 调用open时指定
O_NONBLOCK
标志 - 如果一个描述符已经打开,那么可以调
fcntl
启用O_NONBLOCK标志(管道只有这种方式,没有open调用)
- 调用open时指定
- 系统加于管道和FIFO的唯一限制
- OPEN_MAX 一个进程在任意时刻打开的的最大描述符数 Posix要求至少16
- 可通过执行
ulimit
或limit
(C shell)从shell中修改 - 可通过调用 setrlimit 函数从一个进程中修改
- 可通过执行
- PIPE_BUF 可原子地写往一个管道或FIFO的最大数据量 Posix要求至少512
- OPEN_MAX 一个进程在任意时刻打开的的最大描述符数 Posix要求至少16
- 。。。
- 设置成非阻塞
- 字节流与消息
- 消息边界如何区分
- 带特殊终止序列,例如换行符、一个回车符加上换行符(CR/LF)
- 显式长度
- 每次连接表示一个记录,例如HTTP1.0
- 也可构建更为结构化的消息(加上长度和优先级两个属性)
- 消息边界如何区分
第5章 Posix消息队列
- 消息队列可认为是一个消息链表
- 在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达
- 随内核的持续性
- Posix消息队列和System V消息队列的异同点
- 不同点
- 优先级上的特性
- Posix: 总是返回最高优先级
- System V:可返回任意指定优先级
- 消息对线程的hook(仅Posix)
- 优先级上的特性
- 共有属性
- 具有一个无符号整数优先级(Posix)或者长整数类型(System V)
- 消息的数据部分长度
- 数据本身
- 不同点
- 一些API
- mq_open
- mq_close
- 一个进程终止时,它的所有打开着的消息队列都自动关闭。但其消息队列并不从系统中删除
- mq_unlink
- 从系统中删除用作mq_open第一个参数的某个name
- mq_getattr
- mq_setattr
- mq_send
- mq_receive
- mq_notify
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
- 两种方式
- 产生一个信号
- 创建一个线程来执行一个指定的函数
- 几个规则
- notification非空,当前进程被注册为接受该队列的通知
- notification为空指针,如果当前进程正被注册为接受所指定队列的通知,那么已存在的注册将被注销
- 任何一个时刻,只有一个进程可以被注册为接收某个给定队列的通知
- 只有不存在正被阻塞的mq_receive调用时,通知才会发出
- 消息队列的限制
- mq_mqxmsg 队列中的最大消息数
- mq_msgsize 给定消息的最大字节数
MQ_OPEN_MAX
一个进程能同时拥有的打开着的消息队列的最大数目MQ_PRIO_MAX
任意消息的最大优先级加1
- Posix 实时信号
- 可划分为两个大组
- 值在 SIGRTMIN 和 SIGRTMAX 之间(包括两者)的实时信号,Posix要求至少提供 RTSIG_MAX 种实时信号,该常值的最小值为8
- 所有其他信号:ISGALRM、ISGINT、SIGKILL等
- 如果需要实时行为,我们必须使用SIGRTMIN和SIGRTMAX直接的新的实时信号,而且在安装信号处理程序时必须给sigaction指定SA_SIGINFO标志
- 实时行为
- 信号是排队的。产生几次,就递交几次。
- 当有多个SIGRTMIN到SIGRTMAX范围内的解阻塞信号排队时,值较小的信号先于值较大的信号递交
- 当某个非实时信号递交时,传递给它的信号处理程序的唯一参数是该信号的值。实时信号比其他信号携带更多的信息。
- 一些新函数定义成使用实时信号工作。例如sigqueue函数用于替代kill函数向某个进程发送一个信号
- 实时信号的产生
- SI_ASYNCIO 异步IO请求
- SI_MESGQ 有一个消息被仿制到某个空消息队列中时产生
- SI_QUEUE 信号由
sigqueue
函数发出 - SI_TIMER 信号由使用
timer_settime
函数设置的某个定时器的到时产生 - SI_USER 信号由
kill
函数发出
- 可划分为两个大组
- 使用内存映射IO实现Posix消息队列
- 内存映射IO+Posix互斥锁+条件变量
- 。。。
第6章 System V 消息队列
System V消息队列与Posix消息队列类似。新的应用程序应考虑使用Posix消息队列,不过大量的现有代码使用SystemV消息队列。Posix消息队列缺失的主要特性是从队列中读出指定优先级的消息的能力。这两种消息队列不使用真正的描述符,从而造成在消息队列上使用select或poll的能力。