进程 ---(多任务并行)
2026/9/19 4:44:35 网站建设 项目流程

进程,程序的一次执行过程,也叫做程序的实例

.c编译出来的a.out只是躺在硬盘上的“死文件”,只有被exec加载到内存后,才变成活着的“进程”。(这里的a.out被加载后也是其他程序的子进程)

进程的组成:

栈区(Stack):存放局部变量、函数调用帧、返回地址。

堆区(Heap):动态分配(malloc/calloc),堆内存必须由程序员手动释放。

数据区(Data,又称静态区):
data 段:已初始化的全局变量和静态变量。
.bss 段:未初始化的全局变量

文本区(Text):只读的机器指令

PCB:进程表项(task_struct/PCB):包含PID(进程ID)、PPID(父进程ID)、进程状态(就绪/运行/阻塞)、优先级。还保留有cpu中寄存器的值(切出切入cpu所需),打开的文件描述符,当前目录。

程序的组成:所谓的死文件(数据(变量)+代码)。

pid_t fork(void);
fork后会产生一个新的进程,父进程返回值是子进程的pid,子进程的返回值是0(一次调用,两次返回)。
失败返回-1

fork后,子进程拥有的数据几乎与父进程相同,但是不共享,stactic的变量、全局变量、甚至堆区的变量都是各自用各自的。父子之间难以对互相造成影响,这保证了进程的独立与安全,也带来了信息难以互通的难题。

fork后,谁先运行由调度算法决定,假设父进程先退出,子进程会被系统进程收养。(收养后子进程pid不变),假设子进程先死,而父进程没有回收资源(wait())子进程会变为僵尸态占用内存。

void exit(int status)
功能: 让进程退出,并刷新缓存区
参数:status:进程退出的状态(冷知识,return 0会被编译为exit(0))
exit -> 刷新缓存区 -> atexit注册的退出函数 -> _exit

int atexit(void (*function)(void));
atexit注册了一个函数,令进程在退出(调用exit(0)或者return)前执行此函数。

与_exit(0)相对比,exit(0)是更规范、更温柔的退出。

pid_t wait(int *status);
wait是阻塞操作,会等待任意子进程结束后接收返回值,其通过int *status得到的值并非一个单纯的整型返回值,它被分割为了三部分,0~6位是终止信号编号,7位是是否产生Core Dump,8~15位才是exit的返回值。

故而通过宏来查看其传输的信息:

WIFEXITED(wstatus):检查wstatus的低7位(终止信号位)是否全为0。全为0说明进程是正常退出(exit(),return,_exit()),返回值为真时说明低七位不是0,可以通WIFSIGNALED(wstatus)获得其终止信号编号
WEXITSTATUS(wstatus):获取正常调用exit()退出时exit中的值

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

pid_t waitpid(pid_t pid, int *status, int options);

@pid
<-1 回收指定进程组内的任意子进程(通过setpgid()可以改变进程组id
-1 回收任意子进程,组内外
0 回收和当前调用waitpid一个组的所有子进程,组内
>0 回收指定ID的子进程

@options
0 表示回收过程会阻塞等待
WNOHANG 表示非阻塞模式回收资源。

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

pid_t getppid(void);

返回目前进程的父进程识别码(pid)。

pid_t getpid(void);

返回目前进程的进程识别码(pid)。

若父进程在子进程存活的情况下结束了,会被systemd(pid=1)的子进程systemd收养

【ubuntu 18.04 linux 5.4】

同理,若父进程在有僵尸子进程的情况下结束了,僵尸子进程会被systemd(pid=1)的子进程systemd收尸(wait)

通过创建子进程和exec能够实现一个简单的my_shell

while(1) { char p[1024] = {0}; fgets(p, 1024, stdin); // 读一行命令 int len = strlen(p); p[len-1] = '\0'; // 去掉末尾换行符 if(strcmp(p,"quit")==0 || strcmp(p,"exit")==0) return 0; // 退出循环 char *s[10] = {NULL}; s[0] = strtok(p, " ;:"); // 按空格、分号、冒号分割 if (s[0] == NULL) continue; // 避免空命令 int i = 0; while(s[++i] = strtok(NULL, " ;:")) ; // 继续分割填充 s[1]...s[9] if(fork() == 0) { execvp(s[0], s); // 子进程执行命令 } else { wait(NULL); // 父进程等待 } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询