基础知识
- Linux内核为每个进程分配了PCB(task_struct),并加入对应的列表,对进程的管理,其实就是对PCB、PCB链表的操作
- 增加:
- 分配PCB内存,并填入相应信息
- 复制父进程资源(写时才复制)
- 加入多个链表,如全局链表、调度队列等
- 删除:
- 发送终止信号(如
SIGTERM
)- 释放资源
- 从所有链表中移除对应的PCB
- 父进程通过wait()回收僵尸进程
- 修改:更新PCB内容
- 查询:从链表查找对应指定进程的PCB
-
PCB记录着程序的信息,如:
- 进程ID:唯一标识一个进程。
- 状态:记录当前进程的运行状态(如就绪、运行、等待等)。
- 优先级:用于进程调度,优先级高的进程更容易获得CPU时间。
- 内存指针:指向进程的代码段、数据段、栈段。
- 程序计数器:记录下一条需要执行的指令的地址。
- I/O状态信息:列出与该进程相关的I/O设备和文件。
-
PCB会处于多个链表中,且每个链表都是双向链表;在PCB中嵌入多个链表(如下代码),每次操作PCB都需要操作多个链表
cstruct list_head { struct list_head *next, *prev; }; struct task_struct { // 进程的其他字段 struct list_head tasks; // 所有进程的链表 struct list_head children;// 子进程链表 struct list_head sibling; // 兄弟进程链表 struct list_head ptrace_list; // ptrace 跟踪链表 // 更多链表节点... };
-
万物皆文件:所有进程都被管理在虚拟文件系统/proc/(本身不占据磁盘空间)之下,如pid=1234的进程就对应/proc/1234,里面记录着进程所有信息,如文件打开表记录在/proc/1234/fd/之下,里面存储着链接文件,命名就是文件描述符(句柄),如下所示:
ps指令就是去读取/proc/中的文件,并提取需要输出的信息,按照格式进行输出
-
僵尸进程(defunct):进程已经终止,但是父进程没有调用wait()/waitpid()回收资源,PCB仍保留在链表中
-
孤儿进程:父进程先总是,子进程此时仍在运行,且由init(1号)进程接管,init进程一直都在运行wait(),等待回收孤儿进程 僵尸进程-->孤儿进程:当主进程退出都没有回收僵尸进程的资源,此时僵尸进程会被init接管变为孤儿进程,进而被回收;所以ps看见的僵尸进程(状态为z)都是父进程没有结束而导致的。
基本函数
-
pid_t
getpid
(void); -
pid_t
getppid
(void);- 获取父进程号
- 在终端中使用./启动程序时,此时终端bash就是该进程的父进程
-
int
setpgid
(pid_t pid, pid_t pgid); -
pid_t
getpgid
(pid_t pid);- 获取组号,默认为父进程pid
-
pid_t
fork
(void);- 变量(堆)读时共享,写时拷贝
- 如果共享代码中分配了堆,那子进程、父进程中都需要释放
valgrind ./可执行文件
,来查看内存泄漏
-
void
exit
(int status);- 参数是返回给父进程的参数,低8位有效
- 终结该进程,并回收资源(除了pcb),如堆栈代码段、文件描述符等
- 会刷新io缓冲区,将内存中的内容写入文件中
-
void
_exit
(int status);- 功能一样,但是是直接杀死进程,并清理资源
- io缓冲区的内容直接清理
-
pid_t
wait
(int *status);- 父进程阻塞等待子进程退出,若没有子进程则立即返回,用于回收子进程的pcb并获取状态
- 传入的参数用于接受子进程调用exit传出的参数,status需要搭配成套宏函数使用,来判断返回的状态
- 返回退出的子进程pid
-
pid_t
waitpid
(pid_t pid,int *status,int options);- 和wait差不多,只是wait是阻塞等待,waitpid可配置
- 参数说明
- pid>0 等待指定进程
- pid=0 等待同一组中任何进程
- pid=-1 等待任一子进程
- pid<-1 等待指定进程组中任何子进程,进程组为pid绝对值
- status 和wait中一样
- options=0 阻塞等待
- options=WNOHANG:没有任何已经结束的子进程,则立即返回
- 返回说明:
*0 子进程正常退出
- =0 非阻塞模式,子进程正在运行
- <0 子进程异常退出或已经没有子进程
cint status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) == 0) { //轮询等待回收资源 printf("无子进程退出,父进程继续执行其他任务\n"); sleep(1); }
status
- 父进程无法直接读取子进程中的变量,子进程也无法直接写入父进程变量中,子进程只能写入os分配的空间,然后父进程调用wait、waitpid将数据移动到自己的变量中
- status是按位写入的,整体的指并没有作用
- 低7位记录信号,比如kill -9 pid,就会记录信号为9
- 低第8位没有使用
- 次低8位:进程退出时所对应的退出状态(退出码)
cprintf("waitpid返回的退出码:%d,信号:%d", status >> 8 & 0xFF, status & 0x7F);
-
进程替换函数
exec族函数
调用成功后,就使用指定的程序来替换当前进程,
新老进程,就代码、数据不同
,且会从新程序的main从头执行,老程序的代码全部抛弃
,包括没有执行的部分 调用失败,则出错返回-1exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe,规律如下:
-
l(list) : 表示参数采用列表,NULL作为参数结束标志;
-
v(vector) : 参数用数组;
-
p(path) : 有p自动搜索环境变量PATH,所以第一个参数只用指定名字就行,而不需要路径; - e(env) : 表示自己维护环境变量;
cexecl("/usr/bin/ls","ls","-a","-l",NULL); execlp("ls","ls","-a","-l",NULL); //第一个ls代表路径,需要用此去环境变量中查找路径 //第二个ls代表在命令行中如何调用此指令(程序) char* const argv[] = { "ls", "-a", "-l", NULL }; execv("/usr/bin/ls",argv); execvp("ls",argv);
-