一、说明
在实际应用中,一个程序需要完成很多逻辑功能,有的功能(如数据处理)特别耗时,为了不影响主进程的处理速度,一般在启动一个主进程后,可以同时启动一个或多个进程,或者在需要的时候启动额外的进程去完成一些耗时的或独立的功能,这种应用编程模式叫做多进程。
多进程有如下特点:
- 每个进程都拥有独立的地址空间,子进程崩溃不影响主进程
二、常用API
2.1 fork()
#include <unistd.h>
pid_t fork(void);
作用:创建一个子进程。通过复制调用进程的方式创建一个新进程,新进程称为子进程,调用进程称为父进程。子进程几乎复制了父进程全部的内容,除了以下几点:
- 子进程有自己唯一的进程ID
- 子进程中的父进程ID就是父进程的进程ID
- 子进程不继承父进程的内存锁
- 进程资源占用及CPU时间计数重置为0
返回值:
成功时,在父进程中返回子进程的ID,在子进程中返回0.失败时,返回-1
2.2 getpid()
pid_t getpid(void);
作用:获取当前进程的ID
pid_t getppid(void);
作用:获取当前进程的父进程ID
2.3 waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
作用:等待子进程的状态,回收子进程结束后的资源
参数说明:
pid:
小于 -1:等待进程组号为pid绝对值的任何子进程
-1:等待任何子进程
0: 等待任何进程组ID等于调用进程的子进程
大于0: 等待进程ID等于该值的子进程
option:
WNOHANG 没有子进程退出时立即返回
WUNTRACED 子进程停止时返回
WCONTINUED 处于停止状态的子进程恢复时返回
status: 如果不是NULL的话,返回状态信息,可以通过WIFEXITED(status)、WEXITSTATUS(status)等宏定义获取具体的信息
三、示例分析
3.1 最简单的例子
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t ret_pid = -1;
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
printf("I am child process, ret_pid:%d\n", ret_pid);
sleep(2);
printf("child exit\n");
} else {
printf("I am parent process, ret_pid:%d\n", ret_pid);
printf("wait child exit\n");
waitpid(ret_pid, NULL, 0);
printf("wait child exit end\n");
}
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
clean:
-@rm a.out
测试:
$ ./a.out
I am parent process, ret_pid:76977
wait child exit
I am child process, ret_pid:0
child exit
wait child exit end
3.2 获取(父)进程的ID
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
void child_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
sleep(2);
}
void parent_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
}
int main(int argc, char *argv[])
{
pid_t ret_pid = -1;
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
child_process();
} else {
parent_process();
}
waitpid(ret_pid, NULL, 0);
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
clean:
-@rm a.out
测试:
$ ./a.out
parent_process: pid[77080], parent pid[76350]
child_process: pid[77081], parent pid[77080]
3.3 僵尸进程
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
void child_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
sleep(2);
exit(1);
}
void parent_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
}
int main(int argc, char *argv[])
{
pid_t ret_pid = -1;
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
child_process();
} else {
parent_process();
}
//waitpid(ret_pid, NULL, 0);
while (1) {
sleep(1);
}
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
clean:
-@rm a.out
测试结果:
$ ps aux | grep a.out
zorro 101317 0.0 0.0 4200 684 pts/12 S+ 17:14 0:00 ./a.out
zorro 101318 0.0 0.0 0 0 pts/12 Z+ 17:14 0:00 [a.out] <defunct>
如果杀掉父进程101317后,则僵尸进程101318就会消失。原因是父进程消失后,子进程变成了孤儿进程,孤儿进程由系统回收。
$ kill -9 101317
$ ps aux | grep a.out
3.4 两次fork避免不调用waitpid产生的僵尸进程
在实际应用中,主程序关心子进程的状态,也不想调用waitpid函数。但是如果不调用waitpid,那么在子进程退出后会产生僵尸进程。为了避免这个问题,结合前文提到的“系统会回收孤儿进程”的特性,可以通过两次fork的方法解决。具体原理如下:
- 主进程第一次fork, 产生新的子进程,在主进程中阻塞调用waitpid函数
- 新的子进程再次调用fork,产生新的孙子进程,由孙子进程执行真正的逻辑,子进程则退出
- 由于子进程退出,导致孙子进程成为了孤儿进程。而主进程调用waitpid正常回收子进程的资源,继续执行主进程逻辑
- 当孙子进程执行完功能逻辑后退出时,因为是孤儿进程,则由系统自动回收资源
代码示例:
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
void grandson_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("grandson start\n");
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
sleep(10);
printf("grandson exit\n");
exit(1);
}
void parent_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
}
int main(int argc, char *argv[])
{
pid_t ret_pid = -1;
pid_t ret_pid2 = -1;
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
ret_pid2 = fork();
if (ret_pid2 < 0) {
printf("fork() failed, ret_pid2:%d\n", ret_pid2);
} else if (0 == ret_pid2) {
grandson_process();
} else {
printf("child start\n");
sleep(5);
printf("child exit\n");
exit(1);
}
} else {
parent_process();
printf("before waitpid\n");
waitpid(ret_pid, NULL, 0);
printf("after waitpid\n");
}
while (1) {
sleep(1);
}
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
clean:
-@rm a.out
测试:
$ ./a.out
parent_process: pid[101584], parent pid[100349]
before waitpid
child start
grandson start
grandson_process: pid[101586], parent pid[101585]
child exit
after waitpid
grandson exit
$ ps aux | grep a.out
zorro 101584 0.0 0.0 4200 660 pts/12 S+ 18:12 0:00 ./a.out
zorro 101585 0.0 0.0 4200 88 pts/12 S+ 18:12 0:00 ./a.out
zorro 101586 0.0 0.0 4200 88 pts/12 S+ 18:12 0:00 ./a.out
zorro 101588 0.0 0.1 15944 2276 pts/0 S+ 18:12 0:00 grep --color=auto a.out
$ ps aux | grep a.out
zorro 101584 0.0 0.0 4200 660 pts/12 S+ 18:12 0:00 ./a.out
zorro 101586 0.0 0.0 4200 88 pts/12 S+ 18:12 0:00 ./a.out
zorro 101590 0.0 0.1 15944 2240 pts/0 S+ 18:12 0:00 grep --color=auto a.out
$ ps aux | grep a.out
zorro 101584 0.0 0.0 4200 660 pts/12 S+ 18:12 0:00 ./a.out
zorro 101593 0.0 0.1 15944 2204 pts/0 S+ 18:12 0:00 grep --color=auto a.out
3.5 无名管道pipe
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int g_pipefd[2];
void child_process()
{
char buf[64] = {0};
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
close(g_pipefd[1]);
while (1) {
read(g_pipefd[0], buf, sizeof(buf));
printf("%s: read data:%s\n", __FUNCTION__, buf);
sleep(1);
}
}
void parent_process()
{
pid_t pid = -1;
pid_t ppid = -1;
char buf[64] = {0};
int number = 0;
pid = getpid();
ppid = getppid();
close(g_pipefd[0]);
while (1) {
number++;
snprintf(buf, sizeof(buf), "NO. %02d: hello world", number);
write(g_pipefd[1], buf, strlen(buf));
printf("%s: write data:%s\n", __FUNCTION__, buf);
sleep(1);
}
}
int main(int argc, char *argv[])
{
int ret = 0;
pid_t ret_pid = -1;
ret = pipe(g_pipefd);
if (ret < 0) {
printf("pipe failed\n");
return -1;
}
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
child_process();
} else {
parent_process();
}
waitpid(ret_pid, NULL, 0);
while (1) {
sleep(1);
}
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
clean:
-@rm a.out
测试结果:
parent_process: write data:NO. 01: hello world
child_process: read data:NO. 01: hello world
parent_process: write data:NO. 02: hello world
child_process: read data:NO. 02: hello world
parent_process: write data:NO. 03: hello world
child_process: read data:NO. 03: hello world
parent_process: write data:NO. 04: hello world
child_process: read data:NO. 04: hello world
parent_process: write data:NO. 05: hello world
child_process: read data:NO. 05: hello world
3.6 有名管道
read_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define FIFO_NAME "/tmp/myfifo"
int main(int argc, char *argv[])
{
int ret = 0;
int fd = -1;
char buf[64] = {0};
pid_t ret_pid = -1;
unlink(FIFO_NAME);
ret = mkfifo(FIFO_NAME, 0666);
if (ret < 0) {
printf("mkfifo failed\n");
return -1;
}
fd = open(FIFO_NAME,O_RDONLY);
if (fd < 0) {
printf("open failed\n");
return -1;
}
while (1) {
read(fd, buf, sizeof(buf));
printf("%s: read data:%s\n", __FUNCTION__, buf);
sleep(1);
}
return 0;
}
write_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define FIFO_NAME "/tmp/myfifo"
int main(int argc, char *argv[])
{
int ret = 0;
int fd = -1;
int number = 0;
char buf[64] = {0};
pid_t ret_pid = -1;
fd = open(FIFO_NAME, O_WRONLY);
if (fd < 0) {
printf("open failed\n");
return -1;
}
while (1) {
number++;
snprintf(buf, sizeof(buf), "NO. %02d: hello world", number);
write(fd, buf, strlen(buf));
printf("%s: write data:%s\n", __FUNCTION__, buf);
sleep(1);
}
return 0;
}
Makefile:
all:
gcc -o read.out read_process.c
gcc -o write.out write_process.c
clean:
-@rm *.out
测试:
$ ./read.out
main: read data:NO. 01: hello world
main: read data:NO. 02: hello world
main: read data:NO. 03: hello world
main: read data:NO. 04: hello world
main: read data:NO. 05: hello world
main: read data:NO. 05: hello world
$ ./write.out
main: write data:NO. 01: hello world
main: write data:NO. 02: hello world
main: write data:NO. 03: hello world
main: write data:NO. 04: hello world
main: write data:NO. 05: hello world
3.7 消息队列
msg_queue_send.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "msg_queue.h"
int msg_id = -1;
int main(int argc, char *argv[])
{
int i = 0;
key_t key = -1;
char buf[32] = {0};
msg_event_t msg;
int ret = -1;
struct msqid_ds msq_ds;
key = ftok(KEY_DIR, 0);
printf("%s: key[%d]\n", __FUNCTION__, key);
msg_id = msgget(key, IPC_CREAT | 0666);
if (msg_id < 0) {
printf("%s: msg get failed: %s\n", __FUNCTION__, strerror(errno));
exit(1);
}
printf("%s: msg id:%d\n", __FUNCTION__, msg_id);
memset(&msq_ds, 0, sizeof(msq_ds));
ret = msgctl(msg_id, IPC_STAT, &msq_ds);
if (ret < 0) {
printf("%s: msg get failed: %s\n", __FUNCTION__, strerror(errno));
exit(1);
}
printf("msg_stime: %ld, msg_rtime: %ld, msg_ctime: %ld, msg_qnum: %ld,"
"msg_qbytes:%ld\n",
msq_ds.msg_stime, msq_ds.msg_rtime, msq_ds.msg_ctime, msq_ds.msg_qnum,
msq_ds.msg_qbytes);
while (1) {
memset(&msg, 0, sizeof(msg));
ret = read(STDIN_FILENO, buf, sizeof(buf));
if (ret <= 0) {
printf("%s: read from stdin %s\n", __FUNCTION__, strerror(errno));
break;
}
if (ret < 2) {
continue;
}
buf[ret - 1] = '\0';
msg.msg_type = 1;
strcpy(msg.buf, buf);
ret = msgsnd(msg_id, &msg, sizeof(msg) - sizeof(long int), IPC_NOWAIT);
if (ret < 0) {
printf("%s: msg send failed: %s\n", __FUNCTION__, strerror(errno));
exit(1);
}
if (0 == strcmp(buf, "exit")) {
printf("msg snd exit\n");
break;
}
}
return 0;
}
msg_queue_recv.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "msg_queue.h"
int msg_id = -1;
int main(int argc, char *argv[])
{
int i = 0;
key_t key = -1;
msg_event_t msg;
int ret = -1;
key = ftok(KEY_DIR, 0);
printf("%s: key:[%d]\n", __FUNCTION__, key);
msg_id = msgget(key, 0666);
if (msg_id < 0) {
printf("%s: msg get failed: %s\n", __FUNCTION__, strerror(errno));
exit(1);
}
printf("%s: msg id:%d\n", __FUNCTION__, msg_id);
while (1) {
memset(&msg, 0, sizeof(msg));
msg.msg_type = 0;
ret = msgrcv(msg_id, &msg, sizeof(msg), 0, 0);
if (ret < 0) {
printf("%s: msg get failed: %s\n", __FUNCTION__, strerror(errno));
exit(1);
}
printf("get msg: %s\n", msg.buf);
if (0 == strcmp(msg.buf, "exit")) {
printf("msg get exit\n");
break;
}
}
msgctl(msg_id, IPC_RMID, NULL);
return 0;
}
msg_queue.h:
#ifndef __MSG_QUEUE_H__
#define __MSG_QUEUE_H__
#define KEY_DIR "/tmp"
typedef struct msg_event_s
{
long int msg_type;
char buf[512];
} msg_event_t;
#endif
Makefile:
all:
gcc -o msg_send.out msg_queue_send.c -g
gcc -o msg_recv.out msg_queue_recv.c -g
clean:
-@rm *.out
测试:
$ sudo ./msg_send.out
main: key[65539]
main: msg id:0
msg_stime: 1667902385, msg_rtime: 1667902385,
msg_ctime: 1667901682, msg_qnum: 0,msg_qbytes:32768
hello world
001
$ sudo ./msg_recv.out
main: key:[65539]
main: msg id:0
get msg: hello world
get msg: 001
3.8 共享内存
share_memory_read.c:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
struct my_shared
{
int flag;
char str[1024];
};
int main(void)
{
int running = 1;
int shmid;
void *shared_memory = NULL;
struct my_shared *shared_buff = NULL;
shmid = shmget((key_t)1234, sizeof(struct my_shared), 0666 | IPC_CREAT);
if (shmid < 0) {
perror("fail to shmget");
exit(1);
}
shared_memory = shmat(shmid, NULL, 0);
if(shared_memory == NULL) {
perror("fail to shmat");
exit(1);
}
shared_buff = (struct my_shared *)shared_memory;
shared_buff->flag = 0;
while(running) {
if (shared_buff->flag) {
printf("read message is: %s\n", shared_buff->str);
shared_buff->flag = 0;
if (strncmp(shared_buff->str, "end", 3) == 0) {
running = 0;
}
}
}
if (shmdt(shared_memory) == -1) {
perror("fail to shmdt");
exit(1);
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("fail to shmctl");
exit(1);
}
return 0;
}
share_memory_write.c:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
struct my_shared
{
int flag;
char str[1024];
};
int main(void)
{
int running = 1;
int shmid = -1;
char buffer[512] = {0};
void *shared_memory = NULL;
struct my_shared *shared_buff = NULL;
shmid = shmget((key_t)1234, sizeof(struct my_shared), 0666 | IPC_CREAT);
if (shmid < 0) {
perror("fail to shmget");
exit(1);
}
shared_memory = shmat(shmid, NULL, 0);
if (!shared_memory) {
perror("fail to shmat");
exit(1);
}
shared_buff = (struct my_shared *)shared_memory;
shared_buff->flag = 0;
while (running) {
while (shared_buff->flag) {
printf("wait for other process's reading\n");
sleep(2);
}
printf("Please input some data\n");
fgets(buffer, 512, stdin);
shared_buff->flag = 1;
strncpy(shared_buff->str, buffer, 1024);
if (strncmp(shared_buff->str, "end", 3) == 0) {
running = 0;
}
}
if (shmdt(shared_memory) == -1) {
perror("fail to shmdt");
exit(1);
}
return 0;
}
Makefile:
all:
gcc -o ./write.out share_memory_write.c -g
gcc -o ./read.out share_memory_read.c -g
clean:
-@rm *.out
测试:
$ ./read.out
read message is: hello world
$ ./write.out
Please input some data
hello world
wait for other process's reading
3.9 exec()应用
exec()系列函数可以将当前进程的程序替换成新的可执行程序,而进程号不发生变化。
以下示例代码说明:
通过2次fork()得到孙子进程,在孙子进程内调用exec()函数将运行逻辑替换为new_app.out的程序(该程序创建my.txt并写入进程信息)
multi_process.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
void grandson_process()
{
char buf[64] = {0};
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
snprintf(buf, sizeof(buf), "pid:%d, ppid:%d", pid, ppid);
execl("/home/zorro/voip/Shengy-st/linux-c-demo/039-8-exec/new_app.out",
"new_app.out", buf, NULL);
}
void parent_process()
{
pid_t pid = -1;
pid_t ppid = -1;
pid = getpid();
ppid = getppid();
printf("%s: pid[%d], parent pid[%d]\n", __FUNCTION__, pid, ppid);
}
int main(int argc, char *argv[])
{
pid_t ret_pid = -1;
pid_t ret_pid2 = -1;
ret_pid = fork();
if (ret_pid < 0) {
printf("fork() failed, ret_pid:%d\n", ret_pid);
} else if (0 == ret_pid) {
ret_pid2 = fork();
if (ret_pid2 < 0) {
printf("fork() failed, ret_pid2:%d\n", ret_pid2);
} else if (0 == ret_pid2) {
grandson_process();
} else {
sleep(5);
exit(1);
}
} else {
parent_process();
waitpid(ret_pid, NULL, 0);
}
while (1) {
sleep(1);
}
return 0;
}
new_app.c:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
FILE *fp = NULL;
char buf[128] = {0};
fp = fopen("./my.txt", "w");
if (!fp) {
return -1;
}
if (argc > 1) {
snprintf(buf, sizeof(buf), "%s:%s\n", argv[0], argv[1]);
fwrite(buf, strlen(buf), 1, fp);
}
fclose(fp);
sleep(10);
return 0;
}
Makefile:
all:
gcc -o a.out multi_process.c
gcc -o new_app.out new_app.c
clean:
-@rm *.out
测试:
$ ps aux | grep .out
zorro 50278 0.0 0.0 4200 660 pts/24 S+ 15:29 0:00 ./a.out
zorro 50279 0.0 0.0 4196 88 pts/24 S+ 15:29 0:00 ./a.out
zorro 50280 0.0 0.0 4328 1272 pts/24 S+ 15:29 0:00 new_app.out pid:50280, ppid:50279
$ ps aux | grep .out
zorro 50278 0.0 0.0 4200 660 pts/24 S+ 15:29 0:00 ./a.out
zorro 50280 0.0 0.0 4328 1272 pts/24 S+ 15:29 0:00 new_app.out pid:50280, ppid:50279
$ cat my.txt
new_app.out:pid:50280, ppid:50279