进程章节
- 环境表
- 关于system调用的安全问题
- 终端和作业管控
- 信号
- sigsuspend函数
- 守护进程编程规则
- 多进程问题
- 多线程问题
- IO种类
- 进程通信
- 终端
环境表
每个程序都有一张环境表。环境表是一个字符指针数组,其中每个指针都包含一个以null
结尾的环境变量字符串。全局变量environ包含了该指针数组的地址:
extern char ** environ;
如下图:
关于system调用的安全问题
现有两个可执行程序exec1和exec2,exec1的用户ID为root,并且设置了保存用户ID位;exec2的用户ID为普通用户。exec1程序中使用system调用exec2,当通过普通用户权限调用exec1时,exec2会获取到root的权限。
终端和作业管控
终端登录到启动shell的过程,如下:
会话和进程组,如下:
注意,进程组组长无法创建一个新的会话,调用setsid函数会失败(假如说进程组可以创建会话,那么会导致新会话成员和进程组原来会话产生了冲突(组员属于原来的会话))。
信号
信号排队:在信号屏蔽期间,如果信号多次被递送到进程,则对于不支持排队的信号来说,只会对信号处理一次;对于支持排队的信号,则会处理多次。
注意,在信号处理程序执行的过程中,也可能被其他信号所打断(但是可以在信号处理程序执行过程中设置信号屏蔽集)
。
sigsuspend函数
试想有下面一种场景:
1.屏蔽某个信号。
2.执行临界区代码操作。
3.解除信号得屏蔽。
4.等待信号的到来(pause函数)。
由于第3步和第4步不是原子的,则有可能在3、4步之间发生信号的丢失,第4步将永久等待下去。
int sigsuspend(const sigset_t *mask);
sigsuspend函数就是将解除阻塞和等待这2步合并为原子操作。并且在函数返回时恢复之前的信号屏蔽字(mask临时设置信号屏蔽字)。
守护进程编程规则
- 设置umask。
- fork,父进程exit。
- setsid(解除和控制终端的联系)。
- 更改工作目录;关闭不必要文件描述符;重定向0、1、2至/dev/null。
多进程问题
- 父子进程共享打开的文件描述符。即使在使用了exec系统调用后(当前进程的正文、数据、堆和栈段被替换),父子进程依然共享打开的文件描述符。
多线程问题
- 线程可重入函数不等于信号可重入函数。
- 递归锁可以避免由于信号处理函数导致的死锁问题。
- 每个线程可以设置属于自己的特定的数据(pthread_setspecific)。
- 每个线程都有自己的
信号屏蔽字
,但是所有线程共享信号的处理程序
。 - 在多线程中使用fork时,
子进程中只存在一个线程
(该线程由父进程中调用fork的线程副本构成)。 - 在使用互斥锁时,一个线程对其加锁,另一个线程对其解锁(正常应该是同一个线程加锁、解锁),可能会出现错误。可以封装信号量实现自己的互斥量来支持这种场景。
IO种类
- 非阻塞IO,一般会通过轮询的方式来处理这种IO。
- 阻塞IO,阻塞等待。
- 异步IO,aio_XXX,以aio开头的函数。
- 信号驱动IO,利用信号通知(受限制于不同的操作系统是否支持)。
- IO复用,select、poll、epoll。
异步IO与信号驱动IO的差别在于:信号驱动IO是内核通知我们何时可以启动一个IO操作;而异步IO是由内核通知我们IO操作何时完成。
IO复用配合非阻塞IO可以在一定程度上提升效率,但是会大大增加编码的难度(需要充分考虑接受数据时缓冲区的问题),使用IO复用配合多线程更好(视情况而定)。
进程通信
- 管道,pipe和fifo。
- 消息队列(不推荐使用)。
- 信号量(同步控制,有XSI风格和POSIX风格两种(又分为多进程使用信号量和单进程使用的信号量,初始化方式不同))。
- 共享内存。
- mmap(和共享内存类似,区别是mmap需要与文件关联;匿名mmap或是映射了/dev/zero的mmap只可用于父子进程通信)。
终端
-
波特率:位/每秒
-
终端属性由4个字段标识,配合多种选项,可以组合出无数种终端使用规则。
-
伪终端,指的是对一个应用程序而言,它看上去像一个终端,但事实上它并不是一个真正的终端。下图展示了典型的使用伪终端的结构(进程1打开伪终端主设备,进程2打开伪终端从设备):
对于上图的进程2来说,它好像就是在读写一个终端设备,而实际上他读写的数据都来自进程1。一个伪终端的案例,rlogind服务器: