一、进程的相关概念
1. 进程的由来
- 首先要知晓某 xxx.c 文件 通过 gcc xxx.c -o xxx 指令生成的可执行文件xxx叫做程序,程序是静态的。进程是程序的一次运行活动,也就是程序执行起来,系统中就多了一个进程。
- 父子进程 :A进程创建了B进程,那么A叫做父进程,B叫做子进程。
- 僵尸进程:子进程在父进程前先退出并且父进程没有调用wait()、waitpid()或者waitid()收集子进程的退出状态,子进程变成僵尸进程。
- 孤儿进程:父进程在子进程前先退出,子进程变成孤儿进程,linux避免存在过多孤儿进程,init进程会收留孤儿进程,变成孤儿进程的父进程。
2. 进程的标识符
每一个进程都有一个唯一的非负整数的ID叫做pid,类似于身份证。
- pid = 0 :称为交换进程(swapper)
作用:进程调度(决定当前由那个进程运行,分配cup资源、内存大小等) - pid = 1 :称为初始化进程 (init)
作用:系统初始化
3. 查看系统中的进程指令
-
ps 指令 (通常配合着 grep 指令来查看某一个进程)
ps -aux|grep
-
top 指令(类似于Windows底下的任务管理器)
top
4. C程序的储存空间布局
原文转载链接
文本段(代码段)
初始化数据段 (初始化的全局变量)
未初始化数据段(bss段)(未初始化的全局变量(初始化为零的全局变量)和静态变量)
堆 (动态内存分配)
栈 (形参,局部变量)
命令行参数环境变量
补充:父子进程采用写实拷贝,子进程不修改数据则父子进程共享数据,若子进程进行修改数据则仅将需要改变的数据在子进程地址空间进行一份拷贝并修改,父进程中的数据不发生改变。
二、进程相关API
1.getpid()与getppid()函数
功能 | getpid()获取当前进程的pid; getppid()获取父进程pid |
头文件 | #include <sys/types.h> |
#include <unistd.h> | |
声明 | pid_t getpid(void); pid_t getppid(void) |
参数 | 无 |
返回值 | getpid()返回当前进程的pid; getppid()返回父进程的pid |
2.fork()函数
功能 | 创建子进程 | ||
头文件 | #include <unistd.h> | ||
声明 | pid_t fork(void) | ||
参数 | 无 | ||
返回值 | 成功 | 父进程中返回子进程的pid,子进程中返回0 | |
失败 | 返回-1,则不会创建子进程,并且会适当地设置errno | ||
备注 | 关于fork后父子进程谁先执行取决于进程调度 | ||
用法 | 1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求。 | ||
当这种请求到达时,父进程调用 fork,使子进 程处理此请求。父进程则继续等待下一个服务请求。 | |||
2. 一个进程要执行一个不同的程序。这对 shell是常见的情况。在这种情况下,子进程在 从fork返回后立即调用exec |
3.vfork()函数
功能 | 创建子进程 | ||
头文件 | #include <sys/types.h> #include <unistd.h> | ||
声明 | pid_t vfork(void) | ||
参数 | 无 | ||
返回值 | 成功 | 父进程中返回子进程的pid,子进程中返回0 | |
失败 | 返回-1 | ||
vfork于fork区别 | vfork后子进程直接使用父进程的储存空间不进行拷贝,子进程先执行直到调用exit退出后父进程才执行 |
4.进程退出相关API
正常退出:
- main函数调用return
- 进程调用exit() 属于标准库 (常用)
- 进程调用_exit()或者_Exit() 属于系统调用
异常退出:
- 调用 abort
- 进程收到某些信号,例如 ctrl+C
- 最后一个线程对取消(cancellation )请求做出响应
wait()函数 | |||
---|---|---|---|
功能 | 阻塞等待任意一个子进程退出, 获取子进程终止状态 | ||
头文件 | #include <sys/types.h> #include <sys/wait.h> | ||
声明 | pid_t wait(int *status) | ||
参数status | |||
一个整型数指针(子进程退出状态存放在它指向的地址中 | |||
NULL(不关心退出状态) | |||
返回值 | 成功 | 返回子进程的ID | |
失败 | 返回 -1,失败原因存于errno中 | ||
备注 | 如调用者阻塞而且它有多个子进程,则在其一个子进程终止时立即返回 |
waitpid()函数 | ||||||
---|---|---|---|---|---|---|
功能 | 选择性在一个子进程终止前,waitpid 使其调用者阻塞或非阻塞, 获取子进程终止状态,支持作业控制 | |||||
头文件 | #include <sys/types.h> #include <sys/wait.h> | |||||
声明 | pid_t waitpid(pid_t pid, int *status, int options) | |||||
参数 | pid==-1 | 等待任一子进程。于是在这一功能方面waitpid与wait等效 | ||||
pid > 0 | 等待其进程ID与pid相等的子进程 | |||||
pid== 0 | 等待其组ID等于调用进程的组ID的任一子进程 | |||||
pid < -1 | 等待其组ID等于pid的绝对值的任一子进程 | |||||
status | 一个整型数指针(子进程退出状态存放在它指向的地址中 | |||||
NULL(不关心退出状态) | ||||||
options | 0 | 默认阻塞 | ||||
WNOHANG | 若pid指定的子进程没有结束,则waitpid()函数返回0,不阻塞。若结束,则返回该子进程的ID。 | |||||
WUNTRACED | 若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程 | |||||
返回值 | 成功 | 返回子进程的ID | ||||
失败 | 返回 -1,失败原因存于errno中。如果设置了WNOHANG,还有等待的进程在运行,则失败返回0 | |||||
备注 | 如果使用非阻塞,waitpid不会收集子进程的退出状态,子进程会变成僵尸进程。 |
检查wait和waitpid所返回的终止状态的宏(从而得知程序执行进度)
注意 status为整型变量名
宏 | 说明 |
---|---|
WIFEXITED(status) | 若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(status)取子进程退出编号传送给exit或_exit参数的低8位 |
WIFSIGNALED(status) | 若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号)。对于这种情况,可执行WTERMSIG(status)取使子进程终止的信号编号。另外,SVR4和4.3+BSD(但是,非POSIX.1)定义宏:WCOREDUMP(status)若已产生终止进程的core文件,则它返回真 |
WIFSTOPPED(status) | 若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行 WSTOPSIG(status)取使子进程暂停的信号编号 |
三、demo
思路:
1. fork一个子进程
2. 子进程打印3次信息后退出
3. 父进程调用waitpid收集子进程的退出状态并打印
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int pid = 0;
int sum = 0;
int status = 0;
pid = fork(); //创建进程
if (pid == 0) { //子进程执行
while(1) {
printf("from child pid = %d\n", getpid());
sleep(1);
if (sum == 2) {
exit(3); //子进程退出状态为3
}
sum++;
}
} else if (pid > 0) { //父进程执行
waitpid(pid, &status, 0);
if (WIFEXITED(status)) { //子进程正常退出则为真
printf("Normal exit status = %d\n", WEXITSTATUS(status)); //打印退出状态
} else if (WIFSIGNALED(status)) { //子进程异常退出则为真
printf("Abnormal exit status = %d\n", WTERMSIG(status)); //打印退出状态
}
while (1) {
printf("from father pid = %d\n", getpid());
sleep(1);
}
}
return 0;
}
运行结果: