参考资料:https://blog.csdn.net/dxdxsmy/article/details/6653189
关于子进程回收的方法详解:
以TCPServ 服务程序来说:
1)父进程:负责系统初始化,以及监听(listen),接受连接请求(accept);其中accept 默认阻塞调用。
2)每接受一个连接请求,动态新建(fork)一个子进程,任务完成或客户端断开,服务子进程需要退
出并收回系统资源。
3)根据linux的设计子进程的收回需要父进程参与(wait调用),而此时附进程主要服务工作在监听
客户端连接请求,同时阻塞在(accept)调用,所以父进程自身是无法"分身"去做子进程回收调用。
4)现在的问题在于如何找到父进程的一个服务入口来完成进程回收调用。
方法有2:
1)利用信号机制,由操作系统产生软中断进入进程信号服务程序,执行子进程的回收调用。
在父进程(signal/sigaction)注册SIGCHILD信号,在信号处理函数中执行waitpid调用。
疑问:信号机制可靠吗?特别是应对大频率高负载的动态请求响应场景。
2)在父进程中启动一个新的独立线程pthread,专门负责执行回收调用,此时使用wait阻塞
调用比较合适,如果使用waitpid需要不停的循环等待。注意没有子进程时wait调用立即
返回失败。(父进程在fork子进程时候,可以做一个登记).
5)在很多情况下可以考虑用多线程并发代替多进程并发,省去了很多问题。
进程间通信介绍:
早期UNIX进程间通信方式:
无名管道(pipe)
有名管道 (fifo)
信号(signal)
System V IPC:
共享内存(share memory)
消息队列(message queue)
信号灯集(semaphore set)
套接字(socket)
无名管道:
无名管道特点:
只能用于具有亲缘关系的进程之间的通信
单工的通信模式,具有固定的读端和写端
无名管道创建时会返回两个文件描述符,分别用于读写管道
无名管道创建 – pipe:
#include <unistd.h>
int pipe(int pfd[2]);
成功时返回0,失败时返回EOF
pfd 包含两个元素的整形数组,用来保存文件描述符
pfd[0]用于读管道;pfd[1]用于写管道
示例:子进程1和子进程2分别往管道中写入字符串;父进程读管道内容并打印。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
pid_t pid1, pid2;
char buf[32];
int pfd[2];
if (pipe(pfd) < 0) {
perror(“pipe”); exit(-1);
}
if ((pid1 = fork()) < 0) {
perror(“fork”); exit(-1);
}
else if (pid1 == 0) { // 子进程1
strcpy(buf, “I’m process1”);
write(pfd[1], buf, 32);
exit(0);
}
else { // 父进程
if ((pid2 = fork()) < 0) {
perror(“fork”); exit(-1);
}
else if (pid2 == 0) { // 子进程2
sleep(1); //停顿1秒,可以使得两个子进程不会同时向无名管道写入数据,父进程接收数据时不会发生混乱
strcpy(buf, “I’m process 2”);
write(pfd[1], buf, 32);
}
else { // 父进程
wait(NULL); //子进程结束时由父进程回收
read(pfd[0], buf, 32);
printf(“%s\n”, buf);
wait(NULL);
read(pfd[0], buf, 32);
printf(“%s\n”, buf);
}
}
return 0;
}
读无名管道:
写端存在时:
有数据:read返回实际读取的字节数
无数据:进程读阻塞
写端不存在时:
有数据:read返回实际读取的字节数
无数据:read返回0
写无名管道:
读端存在时:
有空间:write返回实际写入的字节数
无空间:进程写阻塞
获取无名管道的大小的方法:循环写入管道,直到阻塞;统计循环次数。
获取管道大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pfd[2], count = 0;
char buf[1024];
if (pipe(pfd) < 0)
{
perror("pipe");
exit(-1);
}
while ( 1 )
{
write(pfd[1], buf, 1024);
printf("wrote %dk bytes\n", ++count);
}
return 0;
}
读端不存在时:
有空间 和 无空间 管道断裂
验证管道断裂(进程被信号结束):子进程写管道 父进程回收(通过返回值判断管道是否断裂)
管道断裂测试:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int pfd[2];
int status;
pid_t pid;
if (pipe(pfd) < 0) // 创建管道
{
perror("pipe");
exit(-1);
}
close(pfd[0]); // 关闭管道读端
if ((pid = fork()) < 0) // 创建子进程
{
perror("fork");
exit(-1);
}
else if (pid == 0)
{
write(pfd[1], "data", 4); // 写管道
printf("write data to pipe\n");
exit(0);
}
else
{
wait(&status); // 回收子进程
printf("%x\n", status); // 打印子进程退出状态
}
return 0;
}
运行程序并得到如下结果:
linux@ubuntu:~/gaoyong/test$ gcc pipe_distroy.c -Wall
linux@ubuntu:~/gaoyong/test$ ./a.out
d
由此可见程序输出结果为 d ,表示管道断裂。如果程序中管道读端没有被关闭,则输出的结果为 0 。
有名管道特点:
对应管道文件,可用于任意进程之间进行通信
打开管道时可指定读写方式
通过文件IO操作,内容存放在内存中
有名管道创建 – mkfifo:
#include <unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);
成功时返回0,失败时返回EOF
path 创建的管道文件路径
mode 管道文件的权限,如0666
有名管道读写 – 示例:
进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
进程B:循环统计进程A每次写入myfifo的字符串的长度
创建有名管道示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if(mkfifo(“myfifo”, 0666) < 0)
{
perror(“mkfifo”);
exit(-1);
}
return 0;
}
写有名管道示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define N 32
int main(void)
{
char buf[N];
int pfd;
if ((pfd = open(“myfifo”, O_WRONLY)) < 0)
{
perror(“open”); exit(-1);
}
while ( 1 )
{
fgets(buf, N, stdin);
if (strcmp(buf, “quit\n”) == 0) break;
write(pfd, buf, N);
}
close(pfd);
return 0;
}
读有名管道示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define N 32
int main(void)
{
char buf[N];
int pfd;
if ((pfd = open(“myfifo”, O_RDONLY)) < 0)
{
perror(“open”);
exit(-1);
}
while (read(pfd, buf, N) > 0)
{
printf(“the length of string is %d\n”, strlen(buf));
}
printf("peer closed\n");
close(pfd);
return 0;
}
信号机制:
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式
缺省方式
忽略信号
捕捉信号
常用信号:SIGHUP,SIGINT,SIGQUIT,SIGILL,SIGSEV,SIGPIPE
SIGKILL,SIGSTOP,SIGTSTP,SIGCONT,SIGALRM,SIGUSR1,SIGUSR2
信号相关命令 kill / killall:
kill [-signal] pid
默认发送SIGTERM
-signal可指定信号
pid 指定发送对象
killall [-u user | prog]
prog 指定进程名
user 指定用户名
信号发送 – kill / raise:
#include <unistd.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig); //向正在执行的程序发送一个信号
成功时返回0,失败时返回EOF
pid 接收进程的进程号;0代表同组进程; -1代表所有进程
sig 信号类型
信号相关函数 – alarm / pause:
int alarm(unsigned int seconds); //经常用于实现超时检测
成功时返回上个定时器的剩余时间,失败时返回EOF
seconds 定时器的时间
一个进程中只能设定一个定时器,时间到时产生SIGALRM
int pause(void);
进程一直阻塞,直到被信号中断
信号中断后返回-1,errno为EINTR
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
alarm(3);
pause();
printf(“I have been waken up!\n”);
return 0;
}
$ ./a.out
Alarm clock
重要:alarm经常用于实现超时检测
设置信号响应方式 – signal:
#include <unistd.h>
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
成功时返回原先的信号处理函数,失败时返回SIG_ERR
signo 要设置的信号类型
handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
示例:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler (int signo) {
if (signo == SIGINT)
{
printf(“I have got SIGINT!\n”);
}
if (signo == SIGQUIT) {
printf(“I have got SIGQUIT\n”);
}
}
int main()
{
signal(SIGINT, handler);
signal(SIGQUIT, handler);
while ( 1 ) pause();
return 0;
}
进程间通信---System V IPC:
IPC 对象包含: 共享内存、消息队列和信号灯集
每个IPC对象有唯一的ID
IPC对象创建后一直存在,直到被显式地删除
每个IPC对象有一个关联的KEY
ipcs / ipcrm(相关命令)
System V IPC – ftok:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *path, int proj_id);
成功时返回合法的key值,失败时返回EOF
path 存在且可访问的文件的路径
proj_id 用于生成key的数字,不能为0
示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
key_t key;
if ((key = ftok(“.”, ‘a’)) == -1) {
perror(“key”);
exit(-1);
}
……
共享内存:
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
共享内存使用步骤:
创建/打开共享内存
映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
读写共享内存
撤销共享内存映射
删除共享内存对象
共享内存创建 – shmget:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
成功时返回共享内存的id,失败时返回EOF
key 和共享内存关联的key,IPC_PRIVATE(类似无名管道,只有亲缘关系的进程之间才可以使用) 或 ftok生成
shmflg 共享内存标志位 IPC_CREAT|0666(该参数的详细用法:
https://blog.csdn.net/shk242673/article/details/16979135)
示例要求:创建一个私有的共享内存,大小为512字节,权限
为0666
int shmid;
if ((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0) {
perror(“shmget”);
exit(-1);
}
示例要求:创建/打开一个和key关联的共享内存,大小为1024
字节,权限为0666
key_t key;
int shmid;
if ((key = ftok(“.”, ‘m’)) == -1) {
perror(“ftok”);
exit(-1);
}
if ((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0) {
perror(“shmget”);
exit(-1);
}
共享内存映射 – shmat:
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
成功时返回映射后的地址,失败时返回(void *)-1
shmid 要映射的共享内存id
shmaddr 映射后的地址, NULL表示由系统自动映射
shmflg 标志位 0表示可读写;SHM_RDONLY表示只读
注:通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型
示例:在共享内存中存放键盘输入的字符串
char *addr;
int shmid;
……
if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
perror(“shmat”);
exit(-1);
}
fgets(addr, N, stdin);
……
完整示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
key_t key;
char *addr;
int shmid;
if ((key = ftok(“.”, ‘a’)) == -1) //得到Key值
{
perror(“key”);
exit(-1);
}
if ((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0) //创建或者打开共享内存
{
perror(“shmget”);
exit(-1);
}
if ((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) //共享内存映射
{
perror(“shmat”);
exit(-1);
}
fgets(addr, N, stdin);
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
共享内存撤销映射 – shmdt:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
成功时返回0,失败时返回EOF
不使用共享内存时应撤销映射
进程结束时自动撤销
共享内存控制 – shmctl:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
成功时返回0,失败时返回EOF
shmid 要操作的共享内存的id
cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
buf 保存或设置共享内存属性的地址
- IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
- IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
- IPC_RMID:删除msqid标识的消息队列;
共享内存 - 注意事项:
每块共享内存大小有限制
ipcs -l
cat /proc/sys/kernel/shmmax
共享内存删除的时间点
shmctl(shmid, IPC_RMID, NULL) 添加删除标记
nattach 变成0时真正删除
#include < stdio.h>
#include < stdlib.h>
#include < errno.h>
#include < sys/ipc.h>
#include < sys/types.h>
#include < sys/shm.h>
#include < string.h>
#define MAXSIZE 1024
int main()
{
int shmid;
char *p = NULL;
pid_t pid;
#if 0
key_t key;
if ((key = ftok(".", 'a')) == -1) //得到key值
{
perror("ftok");
exit(-1);
}
#endif
if ((shmid = shmget(IPC_PRIVATE, MAXSIZE, 0666)) == -1) //创建共享内存
{
perror("shmget");
exit(-1);
}
if ((pid = fork()) == -1) //创建子结点
{
perror("fork");
exit(-1);
}
if (pid == 0)
{
if ((p = shmat(shmid, NULL, 0)) == (void *)-1) //共性内存映射
{
perror("shmat");
exit(-1);
}
strcpy(p, "hello\n");
system("ipcs -m");
if (shmdt(p) == -1) //共享内存撤销映射
{
perror("shmdt");
exit(-1);
}
system("ipcs -m");
}
else
{
getchar();
if ((p = shmat(shmid, NULL, 0)) == (void *)-1) //共性内存映射
{
perror("shmat");
exit(-1);
}
printf("%s\n", (char *)p);
if (shmctl(shmid, IPC_RMID, NULL) == -1) //添加删除标记
{
perror("RM");
exit(-1);
}
}
return 0;
}
消息队列:
消息队列是System V IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息
消息队列使用步骤:
打开/创建消息队列 msgget
向消息队列发送消息 msgsnd
从消息队列接收消息 msgrcv
控制消息队列 msgctl
消息队列创建/打开 – msgget:
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
成功时返回消息队列的id,失败时返回EOF
key 和消息队列关联的key IPC_PRIVATE 或 ftok(同共享内存)
msgflg 标志位 IPC_CREAT|0666(该参数的详细用法:https://blog.csdn.net/shk242673/article/details/16979135)
示例:
……
int main() {
int msgid;
key_t key;
if ((key = ftok(“.”, ‘q’)) == -1) {
perror(“ftok”); exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) {
perror(“msgget”); exit(-1);
}
……
return 0;
}
消息发送 – msgsnd:
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t size, int msgflg);
成功时返回0,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 消息正文长度
msgflg 标志位 0 或 IPC_NOWAIT
消息格式:
通信双方首先定义好统一的消息格式
用户根据应用需求定义结构体类型
首成员类型为long,代表消息类型(正整数)
其他成员都属于消息正文
示例:
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main() {
MSG buf;
……
buf.mtype = 100;
fgets(buf.mtext, 64, stdin);
msgsnd(msgid, &buf,LEN, 0);
……
return 0;
}
消息接收 – msgrcv:
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, void *msgp, size_t size, long msgtype, int msgflg);
成功时返回收到的消息长度,失败时返回-1
msgid 消息队列id
msgp 消息缓冲区地址
size 指定接收的消息长度
msgtype 指定接收的消息类型
msgflg 标志位 0 或 IPC_NOWAIT
示例:
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main() {
MSG buf;
……
if (msgrcv(msgid, &buf,LEN, 200, 0) < 0) {
perror(“msgrcv”);
exit(-1);
}
……
}
控制消息队列 – msgctl:
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf);
成功时返回0,失败时返回-1
msgid 消息队列id
cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID
buf 存放消息队列属性的地址
- IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中;
- IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
- IPC_RMID:删除msqid标识的消息队列。
消息队列发送示例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
int main()
{
int msgid;
key_t key;
MSG buf;
if ((key = ftok(“.”, ‘q’)) == -1) //得到key值
{
perror(“ftok”);
exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) //创建或者打开消息队列
{
perror(“msgget”);
exit(-1);
}
buf.mtype = 100;
fgets(buf.mtext, 64, stdin);
if (msgsnd(msgid, &buf, LEN, 0) < 0) //消息发送
{
perror("msgsnd");
exit(-1);
}
msgctl(msgid, IPC_RMID, NULL); //删除对应的消息队列
return 0;
}
编程示例:要求两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的消息
消息队列A示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
int msgid;
key_t key;
MSG buf;
if ((key = ftok(“.”, ‘q’)) == -1) //得到key值
{
perror(“ftok”);
exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0) //创建或打开消息队列
{
perror(“msgget”);
exit(-1);
}
while ( 1 )
{
buf.mtype = TypeB;
printf("input > ");
fgets(buf.mtext, 64, stdin);
msgsnd(msgid, &buf, LEN, 0); //发送消息
if (strcmp(buf.mtext, "quit\n") == 0) exit(0);
msgrcv(msgid, &buf, LEN, TypeA, 0); //接收消息
if (strcmp(buf.mtext, "quit\n") == 0) break;
printf("recv message : %s", buf.mtext);
}
msgctl(msgid, IPC_RMID, NULL); //如果不再使用消息队列则删除
return 0;
}
消息队列B示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct {
long mtype;
char mtext[64];
} MSG;
#define LEN (sizeof(MSG) – sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
int msgid;
key_t key;
MSG buf;
if ((key = ftok(“.”, ‘q’)) == -1)
{
perror(“ftok”);
exit(-1);
}
if ((msgid = msgget(key, IPC_CREAT|0666)) < 0)
{
perror(“msgget”);
exit(-1);
}
while ( 1 )
{
msgrcv(msgid, &buf, LEN, TypeB, 0);
if (strcmp(buf.mtext, "quit\n") == 0) break;
printf("recv message : %s", buf.mtext);
buf.mtype = TypeA;
printf("input > ");
fgets(buf.mtext, 64, stdin);
msgsnd(msgid, &buf, LEN, 0);
if (strcmp(buf.mtext, "quit\n") == 0) exit(0);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}