Linux操作系统—进程
进程(process):
(1)进程的定义:正在进行的程序,会去分配内存资源(mem),cpu的调度 ,(flash ssd:固态硬盘)目的:为了实现并发,同一时刻执行多任务
(2)pcb:进程控制块(process control block)结构体
①Pid:进程标识符 身份标识。
②Pwd:当前工作路径:通过chdir设置
③umask 0002 :控制新文件的权限,与新建文件和新建目录有关
④进程打开的文件列表
⑤信号相关的设置,处理异常的io
⑥资源的上限ulimit:(用命令ulimit -a查看,可显示资源上限)
栈的大小(stack size):8k
打开的文件大小(open file):1024
(3)进程和程序的区别:
1.存在时间:①程序存在于./a.out之前
②进程存在于./a.out之后
2.存在状态:①程序是静态的,存储在硬盘的代码,数据集合。
②进程是动态的,程序执行的过程,包括进程的创建,调度(cpu利用时间片轮转,平级),消亡。
(.c ---->a.out---->process(pid))
1)程序是永存的,进程是暂时的
2)进程有程序的状态变化,程序没有
3)进程可以并发,程序没有并发
4)进程与进程之间会存在竞争计算机的资源
5)一个程序可以运行多次,变成多个进程,一个进程可以运行一个或多个程序
3.内存的分布:0-3G是进程的空间(大小3G),3G-4G的内核的空间(大小1G),都是虚拟空间
虚拟地址:物理内存和虚拟内存的地址 映射表1page=4k (mmu管理)
4.进程的分类:
①交互式进程
②拟处理进程:shell脚本
③守护进程:在特定的条件自动触发
(4)进程的状态:
1.基本操作系统:①就绪:等待cpu调度
②运行态:cpu的调度运行中
③阻塞(等待,睡眠):因为某些条件不满足,就会发生阻塞(条件满足后重新进入就绪状态)
2.Linux下的状态:运行态,睡眠态,僵尸态,暂停态
(5)进程的调度:进程上下文切换
调度核心:cpu利用时间片轮转
并发vs并行
①并发:单cpu上快速切换执行多个任务(微观串行,宏观并行)
②并行:多个cpu同时执行多个任务(真正同时运行)
1.内核主要的功能之一就是完成进程调度,硬件,bios,io,文件系统,驱动
2.调度算法:other,idle,rr,fifo
①先来先服务
②短任务优先
③多级任务队列
(6)查询进程相关命令:
1.ps aux:查看进程相关信息
状态符 | 含义 | 说明 |
---|---|---|
R | 运行/就绪态 | 正在CPU执行或等待调度 |
S | 可中断睡眠态 | 等待事件(可被信号唤醒) |
D | 不可中断睡眠态 | 等待I/O完成(不可唤醒) |
T | 暂停态 | 被信号停止(如SIGSTOP) |
Z | 僵尸态 | 进程已终止但资源未回收 |
(无) | 结束态 | 资源完全回收 |
进程的调度与上下文切换
2.top:根据CPU占用率查看进程相关信息(退出用q键)
3.kill/killall :发送一个信号
①kill -2 PID:发送信号+PID对应的进程,默认接收者关闭
②kill -9 PID:发送信号,进程名对应的所有进程
③killall -9 a.out:终止所有的a.out
进程原语:
(1)fork():产生一个子进程(父进程的副本)//并发
pid_t ret=fork();
1.特性:
①一次调用,返回两次(父和子)---从fork后开始程序至少执行两次(父子分开执行)
ret>0:执行父进程,ret代表子进程的id号
ret==0:执行子进程
②父进程和子进程执行顺序不确定(如果非要确定那个要先运行,需要IPC机制。)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc, char *argv[])
{pid_t ret = fork(); // 创建子进程,返回两次if(ret > 0) // 父进程分支(ret = 子进程PID){while(1) // 父进程无限循环{printf("father,发视频....\n");sleep(1); // 暂停1秒控制输出频率}}else if(0 == ret) // 子进程分支(ret = 0){while(1) // 子进程无限循环{printf("child,接收控制....\n");sleep(1);}}else // fork失败{perror("fork err\n");return 1;}return 0;
}
理想运行结果:
father,发视频…
child,接收控制…
father,发视频…
child,接收控制…(交替输出,顺序取决于调度)
③变量隔离:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int a = 20; // 全局变量
int main()
{pid_t ret = fork();if(ret > 0) // 父进程{sleep(3); // 确保子进程先修改printf("father ,a is %d\n", a); // 输出20}else if(0 == ret) // 子进程{a += 10; // 修改副本printf("child ,a is %d\n", a); // 输出30}printf("a is %d\n", a); // 父进程输出20,子进程输出30return 0;
}
理想运行结果:
child ,a is 30
a is 30
father ,a is 20
a is 20因为子进程在fork()复制完父进程的数据后,两者单独运行,数据不共享
(2)getpid()和getppid()
1.getpid():获得当前进程的id号
pid_t getpid(void);
2.getppid():功能:获得父进程的id号
pid_t getppid(void)
#include <stdio.h>
#include <unistd.h>int main()
{pid_t ret = fork();if(ret > 0) // 父进程{sleep(3);printf("father ,pid:%d ppid:%d\n", getpid(), getppid());}else if(0 == ret) // 子进程{printf("child ,pid:%d ppid:%d\n", getpid(), getppid());}printf("pid:%d ppid:%d\n", getpid(), getppid());return 0;
}
理想运行结果:
child ,pid:26205 ppid:26204
pid:26205 ppid:26204
father ,pid:26204 ppid:3199
pid:26204 ppid:3199
(3199为终端父进程ID)
(3)多重fork()
#include <stdio.h>
#include <unistd.h>int main()
{fork() && fork() || fork(); // 逻辑运算符触发5个进程(注意逻辑运算的截断)printf("pid:%d\n", getpid());return 0;
}
进程关系:
1个父进程 → 生成4个子进程(含1个孙进程)
理想运行结果:
pid:1001
pid:1002
pid:1003
pid:1004
pid:1005
(共5行输出,PID顺序不定)
(7)父子进程的关系:
1.子进程是父进程的副本(复制品)。子进程获得父进程的代码段和数据段,堆,栈,正文段共享。
写时复制:其核心思想是多个调用者最初共享同一份资源,只有在某个调用者试图修改资源时(code,data,heap,stack),系统才会真正复制一份副本给该调用者(子进程)。
2.关键区别:
①fork()返回值不同(父进程>0,子进程=0)
②PID/PPID不同
③资源计数器重置(如文件描述符)
3.执行起点:子进程从fork()后开始执行
4.典型场景:
①网络服务:父子进程执行相同代码
②程序替换:一个程序需要执行一个不同的程序:fork() + 【exec()执行新程】
(8)进程的终止:8个情况
(9)exit()和_exit():指终止程序运行的函数或命令
#include <stdio.h>
#include <unistd.h>int main()
{printf("hello"); // 无换行符,缓冲区未刷新// exit(0); // 会刷新缓冲区 → 输出"hello"_exit(0); // 不刷新缓冲区 → 无输出return 0;
}
1.关键区别:
①exit(0)
:先刷新I/O缓冲区,再执行清理函数,最后调用exit()
②_exit(0)
:立即终止,不处理缓冲区和清理函数
2.理想结果:①用
exit(0)
时输出hello
②用
_exit(0)
时无输出
(10)进程的退出:
1.孤儿进程:父进程先消亡,子进程就是孤儿进程,子进程自动由init
接管
init回收机制原理
- 当进程终止时,内核会检查其是否有子进程仍在运行
- 内核将这些子进程的PPID改为1
- init进程定期调用
wait()
系统调用回收已终止的子进程 - 资源(如内存、文件描述符等)被系统回收
int main()
{pid_t ret = fork();if(ret > 0) // 父进程{sleep(3); exit(0); // 父进程提前终止}else // 子进程{while(1) { printf("child,接收控制....pid:%d ppid:%d\n", getpid(), getppid());sleep(1);}}return 0;
}
现象:子进程PPID变为1(
init
进程ID)
2.僵尸进程:子进程先消亡,就是僵死进程,内存空间被释放,也不再被调用。需要注意的pcb块(在内核空间),父进程需要利用wait(),waitpid()回收
int main()
{pid_t ret = fork();if(ret > 0) // 父进程{while(1) { printf("father,发视频....pid:%d ppid:%d\n", getpid(), getppid());sleep(1);}}else // 子进程{sleep(3); exit(0); // 子进程终止,但父进程未回收 → 僵尸}return 0;
}
现象:
ps aux
中子进程状态为Z
,资源未释放
资源回收:僵尸进程需父进程调用wait()
/waitpid()
回收;孤儿进程由init
自动回收
(11)进程回收:
父进程通过
wait
/waitpid
回收子进程资源,避免僵尸进程。核心功能包括:
- 阻塞等待子进程退出
- 获取子进程退出状态
- 释放内核 PCB 资源
1.wait:
(父进程等待子进程回收)由父进程调用,回收子进程的pcb会发生阻塞,父进程回收资源的时候,子进程没有退出,父进程就需要要等待子进程结束后回收
pid_t wait(int *status);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main()
{pid_t pid = fork();if(pid > 0) // 父进程{printf("father pid:%d, child pid:%d\n", getpid(), pid);pid_t recycle_pid = wait(NULL); // 阻塞等待任意子进程printf("recycle end... pid:%d\n", recycle_pid);}else if(0 == pid) // 子进程{int i = 3;while(i--){printf("i'm processing...\n");sleep(1); // 模拟工作}exit(0); // 正常退出}else {perror("fork");return 1;}return 0;
}
理想输出:
father pid:12345, child pid:12346
i'm processing...
i'm processing...
i'm processing...
recycle end... pid:12346
- 关键点:
- 父进程在
wait(NULL)
处阻塞直到子进程退出 - 子进程退出后父进程立即回收资源
- 父进程在
2.waitpid
pid_t waitpid(pid_t pid, int *status, int options);
- 可以指定等待的子进程 PID(通过
pid
参数),或一组子进程(通过pid
的特定值)。 - 支持非阻塞模式(通过
options
参数设置WNOHANG
)。 - 更灵活,适用于需要精确控制子进程等待的场景。
- 阻塞调用进程,直到任意一个子进程终止。
- 无法指定等待的子进程,只能等待第一个终止的子进程。
- 返回终止子进程的 PID,并通过参数获取退出状态。
参数说明
waitpid
的 pid
参数
pid > 0
:等待指定 PID 的子进程。pid = -1
:等待任意子进程(等效于wait
)。pid = 0
:等待与调用进程同进程组的任意子进程。pid < -1
:等待进程组 ID 为|pid|
的任意子进程。
options
参数
WNOHANG
:非阻塞模式,如果没有子进程终止,立即返回 0。 WUNTRACED
:返回已停止(未终止)的子进程状态(需配合信号使用)。
4.状态检查宏
WIFEXITED(status)
:子进程正常退出时返回真。WEXITSTATUS(status)
:获取子进程的退出码(仅当WIFEXITED
为真时有效)。WIFSIGNALED(status)
:子进程因信号终止时返回真。WTERMSIG(status)
:获取导致终止的信号编号(仅当WIFSIGNALED
为真时有效)。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main()
{pid_t pid = fork();if(pid > 0) // 父进程{printf("father pid:%d, child pid:%d\n", getpid(), pid);int status;while(1){// 非阻塞等待指定子进程pid_t recycle_pid = waitpid(pid, &status, WNOHANG);if(pid == recycle_pid) // 回收成功{printf("recycle end... pid:%d\n", recycle_pid);if(WIFEXITED(status)){printf("child ret value is %d\n", WEXITSTATUS(status));}break;}else if (0 == recycle_pid) // 子进程未结束{printf("子进程未结束...\n");usleep(500000); // 休眠 0.5 秒}else {printf("wait error\n");break;}}}else if(0 == pid) // 子进程{int i = 10;while(i--){printf("i'm processing...pid:%d\n", getpid());sleep(1);}exit(20);}else {perror("fork");return 1;}return 0;
}
理想输出:
father pid:12345, child pid:12346
子进程未结束...
i'm processing...pid:12346
...(父进程持续打印"子进程未结束",子进程循环 10 次)...
i'm processing...pid:12346
recycle end... pid:12346
child ret value is 20
- 关键点:
- 父进程通过
WNOHANG
实现非阻塞轮询 - 子进程运行期间父进程可执行其他任务(此处仅打印提示)
- 父进程通过
5.注意事项
- 如果调用进程没有子进程,
wait
会立即返回错误(ECHILD
)。 - 使用
waitpid
时,若指定的pid
不存在或无权访问,会返回错误。 - 僵尸进程(已终止但未
wait
的进程)会占用系统资源,应确保及时回收。
函数 | 特点 |
---|---|
wait(&status) | 阻塞等待任意子进程退出,等效于 waitpid(-1, &status, 0) |
waitpid(pid, ...) | 可指定回收目标进程,支持非阻塞模式 (WNOHANG ) |
回调函数:atexit
int atexit(void (*function)(void));
参数:function:函数指针,指向void返回值void参数的函数指针
功能:注册进程退出前执行的函数
关键特性
- 注册的函数在程序正常终止(如
main
返回或调用exit
)时执行。 - 注册顺序为LIFO,最后注册的函数最先执行。
- 至少支持32个函数的注册(具体数量由实现定义)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>FILE* fp;
char* p;// 清理函数:释放资源并打印信息
void clean()
{printf("clean fun, p is %s\n", p); // 打印字符串内容fclose(fp); // 关闭文件free(p); // 释放堆内存
}int main(int argc, char **argv)
{atexit(clean); // 注册清理函数fp = fopen("1.txt", "r"); // 打开文件p = malloc(50); // 分配堆内存strcpy(p, "hello"); // 填充字符串printf("process will end..\n"); // 主程序结束提示return 0; // 触发 atexit 注册的 clean 函数
}
(12)exec族:替换当前进程的代码段和数据段,执行新程序。不创建新进程,仅替换执行内容。
函数名 | 函数原型 | 核心特点 |
---|---|---|
execl | int execl(const char *path, const char *arg, ..., (char *)NULL); | 1. path 需指定可执行文件的完整路径(如 /bin/ls )2. 参数列表可变,最后必须以 (char *)NULL 结尾(标记参数结束) |
execlp | int execlp(const char *file, const char *arg, ..., (char *)NULL); | 1. file 可只给文件名(如 ls ),会自动从 PATH 环境变量中查找可执行文件2. 参数列表以 (char *)NULL 结尾 |
execle | int execle(const char *path, const char *arg, ..., (char *)NULL, char *const envp[]); | 1. path 需完整路径2. 最后一个参数 envp 是自定义环境变量数组(替代当前进程的环境变量) |
execv | int execv(const char *path, char *const argv[]); | 1. path 需完整路径2. 参数通过字符串数组 argv 传递,数组最后一个元素必须是 NULL |
execvp | int execv(const char *file, char *const argv[]); | 1. file 可只给文件名(依赖 PATH 查找)2. 参数通过数组 argv 传递,末尾需 NULL |
execve | int execve(const char *path, char *const argv[], char *const envp[]); | 1. 唯一的系统调用(其他 5 个都是库函数,最终调用 execve )2. 支持自定义环境变量数组 envp |