僵尸进程
僵尸态:子进程结束运行后,内核没有立即释放该进程的进程表表项,用以满足父进程后续对子进程退出信息的查询
在子进程结束之后、父进程读取退出状态之前(父进程正常运行),称之为僵尸态;父进程退出之后(下文)、子进程退出之前,称之为僵尸态
父进程结束或异常终止,子进程仍在运行时,改变其 PPID 为 1(init 进程),由 init 进程等待子进程结束
一般通过wait()
和waitpid()
处理僵尸进程
1 |
|
wait()
将父进程阻塞,直到任意一个子进程结束运行。返回结束运行的子进程的 PID,并保存退出状态信息到参数stat_loc
指向的内存中
waitpid()
通过参数pid
指定等待的子进程,取值为-1 则表示如同wait()
一样等待任意子进程结束;参数stat_loc
保存退出状态信息;参数options
指定控制行为,一般取值为WNOANG
,表示非阻塞调用
进程间通信
管道
父子进程通过管道传递数据,且管道只能用于有关联的两个进程之间。本质上是pipe()
通过管道文件描述符fd[1]
向内核缓冲区写入数据,fd[0]
从内核缓冲区读出数据,为半双工方式。缓冲区大小有限,写满或者读空时,需要等待读出或者写入
管道容量的默认大小是 65536B,可以通过
fcntl()
修改容量
1 |
|
通过构建两个管道实现双向通信;或者通过 socket 编程提供的sockpair()
1 |
|
前三个参数与socket()
的参数含义一致,但domain
只能使用 UNIX 本地域协议族AF_UNIX
;文件描述符fd
是既可读又可写的
有名管道
保存在文件系统中
1 | mkfifo myPipe |
信号量
用户进程通过使用 OS 提供的一对原语来对信号量进行操作,实现进程同步与互斥
原语:一种特殊的程序段,执行时不可被中断,由关/开中断指令实现
一对原语:
- wait(S)原语,可以理解为函数,信号量 S 是调入的参数,可以写作 P(S),相当于进入临界区
- signal(S)原语,可以写作 V(S),相当于退出临界区
信号量可以看作变量(整数或者复杂的记录型变量),可以用信号量来表示系统中某种资源的数量,例如一台打印机的信号量初值为 1
可以看作一种计数器,用来为多个进程提供对共享数据对象的访问
❗ 通过semget()
将某个信号量生成信号量标识符,通过semop()
执行 P、V 操作,通过semctl()
控制信号量的某些属性
semget()
创建一个新的信号量集,或获得一个已存在的信号量集
1 |
|
成功时返回一个正整数值,作为信号量集的标识符;错误时返回-1 并设置 errno
key
:用来标识一个全局唯一的信号量集,它代表程序可能使用的某个资源,通过信号量通信的进程使用相同的key
来 | 创建 | 获取 | 该信号量。通过semget()
和key
,由系统生成一个信号量标识符,程序通过信号量标识符来使用信号量
num_sems
:指定要 | 创建 | 获取 | 的信号量集中信号量的数目。如果是创建信号量,则该值必须被指定,一般都是 1;如果是获取已经存在的信号量,则可以把它设置为 0
sem_flags
:指定一组标志。作为参数的信号量不存在时,想要创建一个新的信号量,可以将sem_flags
和值IPC_CREAT
按位或操作;而IPC_CREAT | IPC_EXCL
可以创建一个新的、唯一的信号量,如果信号量已存在,报错
下图为sem_flags
可以的取值,一般直接设置为0666
例:
1 | // 创建信号量 |
semctl()
控制信号量信息
1 |
|
sem_id
:表示semget()
返回的信号量集的标识符
sem_num
:表示信号量集中信号量的编号,0 为第一个。一般来说都设置为 0
command
:表示要执行的命令,第三个参数取值SETVAL
(把信号量初始化为一个已知的值)或IPC_RMID
(删除一个已经无需继续使用的信号量标识符);第四个参数通常是一个union semum
结构
1 | union semun { |
一般需要利用semctl()
初始化信号量,这是使用前必须做的
例:
1 | // 创建信号量 |
semop()
改变信号量的值,即 P、V 操作,实际上是对内核变量的操作
1 |
|
sem_id
:semget()
返回的信号量集的标识符
sem_ops
:指向sembuf
结构体数组
1 | struct sembuf { |
sem_num
:表示信号量集中信号量的编号,0 为第一个。一般来说,传入的单个信号量,此时设置为 0;如果使用信号量集,根据编号设置
sem_op
:表示操作类型。一般用1
表示 V 操作(即退出临界区);用-1
表示 P 操作(即进入临界区)
sem_flg
:IPC_NOWAIT
或SEM_UNDO
。IPC_NOWAIT
表示无论调用是否成功,立刻返回,类似于非阻塞 I/O;SEM_UNDO
表示操作系统跟踪信号,进程未释放信号量而终止时,操作系统释放信号量
num_sem_ops
:sem_ops
中信号量的个数
例:
1 | // 创建信号量 |
共享内存
在通信进程的虚拟内存空间中,拿出一块虚拟地址空间,映射到相同的物理内存中
- 一般利用 | 信号量 | 记录锁 | 互斥量 | 来同步访问共享内存
❗ 通过shmget()
申请一段新的共享内存,通过shmat()
关联,shmdt
分离;shmctl()
控制共享内存的某些属性
shmget()
创建一个新的共享内存,或获得一个已存在的共享内存
1 |
|
成功时返回一个正整数值,作为共享内存的标识符;错误时返回-1 并设置 errno
key
:用来标识一个全局唯一的共享内存,通过shmget()
和key
,由系统生成一个共享内存标识符,程序通过共享内存标识符来使用共享内存
size
:以 B 为单位指定需要共享的内存容量
shmflg
:与信号量中semget()
中的参数semflg
类似,也是可以利用IPC_CREAT
做按位或操作
例:
1 | // 创建共享内存记录结构体shared_test |
shmat()
在进程中关联新创建的共享内存;shmdt()
在进程中分离共享内存
1 |
|
shm_id
:shmget()
中返回的共享内存标识符
shm_addr
:指定共享内存关联到当前进程的地址,一般设置为 0,表示由系统决定
shmflg
:一组标志位,一般设置为 0,配合shm_addr
设置为 0,来由系统决定关联地址
shmat()
返回指向共享内存所关联的地址的指针;失败返回-1。类似于malloc()
申请动态内存
shmdt()
由关联动态内存的指针,在本进程中分离动态内存(只是本进程不能使用,并不是删除动态内存)
例:
1 | // 创建共享内存记录结构体shared_test |
shmctl()
控制共享内存的属性
1 |
|
shm_id
:shmget()
中返回的共享内存标识符
command
:表示要执行的命令
IPC_STAT
:复制共享内存的当前关联值到shmid_ds
中
IPC_SET
:当进程拥有权限时,设置shmid_ds
中的属性到共享内存的关联值中
IPC_RMID
:删除当前共享内存
buf
:指向用于复制或更改属性的shmid_ds
例:
1 | // 创建共享内存记录结构体shared_test |
❗:一般在操作共享内存时,可以搭配信号量实现读写的互斥访问;但是有的程序可以放弃使用信号量,让多个进程同时进行读操作,提高效率,这就是进程同步问题的读者-写者问题
消息队列
直接通信方式:发送进程利用发送原语将消息直接发送到接收进程的消息缓冲队列,由接收进程利用接收原语接收消息
间接通信方式:发送进程利用发送原语将消息发送到中间实体(信箱),接收进程利用接收原语将信箱中属于自己的消息接收;消息的消息头中包含了发送和接收进程的 ID 等内容,不用担心接收错误
- 消息队列可以独立于发送进程和接收进程而存在
- 接收程序可以选择接收消息队列中的特定类型的信息
❗ 通过msgget()
创建一个消息队列;利用msgsnd()
挂载消息到队列上,利用smgrcv()
从队列上摘取消息;msgctl()
控制消息队列的某些属性
msgget()
创建一个消息队列,或者获取一个已有的消息队列
1 |
|
成功时返回一个正整数值,作为消息队列的标识符;错误时返回-1 并设置 errno
key
:用来标识一个全局唯一的消息队列,通过msgget()
和key
,由系统生成一个消息队列标识符,程序通过消息队列标识符来使用消息队列
msgflg
:与信号量中semget()
中的参数semflg
类似,也是可以利用IPC_CREAT
做按位或操作;当消息队列存在时,IPC_CREAT
被忽略,返回标识符
例:
1 | // 创建消息队列 |
msgsnd()
挂载消息到队列上,smgrcv()
从队列上摘取消息
1 |
|
msg_id
:msgget()
中返回的消息队列标识符
msg_ptr
:指向一个准备 | 发送 | 存储 | 的消息,消息必须被定义为msgbuf
这种结构
msg_sz
:表示消息数据的长度。如上所示则长度为 512;如设置为 0 则表示没有消息
msgflg
:一组标志位,可以按位或
在
msgsnd()
中:默认为 0,消息队列满时将阻塞,直到成功;若设置为IPC_NOWAIT
,表示为非阻塞方式,消息队列满时将立即返回并设置 errno在
msgrcv()
中:设置为IPC_NOWAIT
,表示非阻塞方式;设置为MSG_NOERROR
,表示消息长度超过msg_sz
时截断;设置为MSG_EXCEPT
,且msgtype
大于 0,表示接收消息队列中第一个非msgtype
类型的消息
msgtype
:指定接收何种类型的消息
等于 0:读取消息队列中的第一个消息
大于 0:读取消息队列中第一个类型为
msgtype
类型的消息(msgflg
指定了MSG_EXECPT
的情况下则不同)小于 0:读取消息队列中第一个类型值比
msgtype
小的消息
两个系统调用成功时返回 0;失败则返回-1 并设置 errno
例:
1 | struct msgbuf { |
msgctl()
控制消息队列的某些属性
1 |
|
msg_id
:msgget()
中返回的消息队列标识符
command
:表示要执行的命令
IPC_STAT
:复制消息队列的当前关联值到msgid_ds
中
IPC_SET
:当进程拥有权限时,设置msgid_ds
中的属性到消息队列的关联值中
IPC_RMID
:删除当前消息队列
buf
:指向用于复制或更改属性的msgid_ds
例:
1 | struct msgbuf { |
在进程间传递文件描述符
父进程打开的文件,通过fork()
后将文件描述符复制到子进程中,但是子进程传递到父进程则不行
要点在于,传递文件描述符,并不是简单的将文件描述符的值复制给另一个进程,而是通过文件描述符指向相同的文件。进程有file_struct
中的指针数组的索引做文件描述符,应当在进程间传递这样的结构
可以通过 UNIX 域 socket 在进程间传送,参考sockpair()