进程的概念
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();//getpid 获取线程号
printf("my pid is %d\n",pid);
while(1);
return 0;
}
创建进程函数 fork 的使用
fork()
是 Unix/Linux 系统中创建新进程的核心函数,它像一个”进程分身术”。
一句话理解
fork()
创建当前进程的完全副本(子进程),父子进程从fork()
返回处开始并行执行后续代码。
函数原型
#include <unistd.h> // 必须包含的头文件
pid_t fork(void); // 无参数,返回进程ID
返回值(关键!)
返回值 | 含义 |
---|---|
> 0 | 父进程中返回子进程的ID |
== 0 | 子进程中返回0 |
< 0 | 创建失败(错误) |
执行流程
#include <stdio.h>
#include <unistd.h>
int main() {
printf("准备fork (PID=%d)\n", getpid());
pid_t pid = fork(); //fork();创建一个子进程 //fork返回的值就是子进程的pid//
//fork之后,父进程和子进程都会从fork()返回的地方继续执行,所以后面的代码会被两个进程分别执行。
// 注意:从这里开始两个进程并行执行
if (pid < 0) { //创建失败(错误)
// 错误处理
perror("fork失败");
} else if (pid == 0) {//子进程中返回0
// 子进程执行的代码
printf("我是子进程 PID=%d\n", getpid());
} else {//父进程中返回子进程的ID
// 父进程执行的代码
printf("我是父进程 PID=%d, 子进程PID=%d\n", getpid(), pid);//pid变量是fork返回的子进程的pid
}
// 父子进程都会执行到这里
printf("这条消息来自%s进程\n", (pid == 0) ? "子" : "父");
return 0;
}
- 在父进程中,pid变量等于子进程的PID(大于0),所以进入else分支,打印:”我是父进程 PID=父进程ID, 子进程PID=子进程ID”
- 在子进程中,pid变量等于0,所以进入else if (pid == 0)分支,打印:”我是子进程 PID=子进程ID”
可能的输出:
准备fork (PID=1234)
我是父进程 PID=1234, 子进程PID=1235 //fork返回的值是子进程的pid
这条消息来自父进程
我是子进程 PID=1235
这条消息来自子进程
核心
- 子进程共享父进程的内存空间
- 只有当某个进程修改数据时,才会真正复制内存
- 高效节省内存资源
vfork创建进程
vfork子进程里面,会修改父进程的值,而fork创建的子进程不会修改父进程代码的值,只有当某个进程修改数据时,才会真正复制内存
fork
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0)
{
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3)
{
exit(0);
}
}
}
return 0;
}
vfork –> 保证子进程先运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3)
{
exit(0);
}
}
}
return 0;
}
重点:
- vfork 直接使用父进程存储空间,不拷贝
- vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
- vfork子进程里面,会修改父进程的值,而fork创建的子进程不会修改父进程代码的值,只有当某个进程修改数据时,才会真正复制内存
进程退出
父进程等待子进程退出
子进程退出状态不被收集,变成僵死进程(僵尸进程)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3)
{
exit(0);
}
}
}
return 0;
}
父进程等待子进程退出,并收集子进程的退出状态
wait函数
wait()
是 Unix/Linux 中用于进程同步和资源回收的关键函数,它像一个”耐心的父母等待孩子结束”。
函数原型
#include <sys/types.h>
#include <sys/wait.h> // 必须包含的头文件
pid_t wait(int *status); // status: 用于存储子进程退出状态
核心功能
- 阻塞等待:父进程暂停执行,直到有子进程结束
- 回收资源:释放子进程占用的系统资源
- 获取状态:了解子进程如何退出(正常/异常) 状态码解析(重点!)
wait(&status)
中的status
包含丰富信息,需用宏来解析:
宏函数 | 作用 | 示例 |
---|---|---|
WIFEXITED(status) | 子进程是否正常退出 | if (WIFEXITED(status)) |
WEXITSTATUS(status) | 获取退出状态码 | printf("%d", WEXITSTATUS(status)) |
WIFSIGNALED(status) | 是否被信号终止 | if (WIFSIGNALED(status)) |
WTERMSIG(status) | 获取终止信号编号 | printf("%d", WTERMSIG(status)) |
WIFSTOPPED(status) | 是否被暂停 | if (WIFSTOPPED(status)) |
父进程使用 wait()
阻塞等待任意子进程结束,并获取其退出状态,避免产生”僵尸进程”。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0)
{
wait(NULL);
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3)
{
exit(0);
}
}
}
return 0;
}
获取子进程退出状态
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0)
{
wait(&status);//存储子进程退出状态
printf("child quit, child status = %d\n",WEXITSTATUS(status));
//WEXITSTATUS获取退出状态码的宏
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print,pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt ++;
if(cnt == 3)
{
exit(3);//设置退出状态码
}
}
}
return 0;
重点:
- 使用wait 父进程会阻塞,等待子进程运行完在运行、
- wait的参数是 整型数指针
- wait的参数可以为空(NULL)–> 不关心退出状态
wait(NULL);
- status: 用于存储子进程退出状态
wait(int *status); // status: 用于存储子进程退出状态
- status获取到的状态 要用宏来解析
宏函数 | 作用 | 示例 |
---|---|---|
WIFEXITED(status) | 子进程是否正常退出 | if (WIFEXITED(status)) |
WEXITSTATUS(status) | 获取退出状态码 | printf("%d", WEXITSTATUS(status)) |
WIFSIGNALED(status) | 是否被信号终止 | if (WIFSIGNALED(status)) |
WTERMSIG(status) | 获取终止信号编号 | printf("%d", WTERMSIG(status)) |
WIFSTOPPED(status) | 是否被暂停 | if (WIFSTOPPED(status)) |