当前位置: 首页 > news >正文

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回收机制原理

  1. 当进程终止时,内核会检查其是否有子进程仍在运行
  2. 内核将这些子进程的PPID改为1
  3. init进程定期调用wait()系统调用回收已终止的子进程
  4. 资源(如内存、文件描述符等)被系统回收
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族:替换当前进程的代码段和数据段,执行新程序。不创建新进程,仅替换执行内容。

    函数名函数原型核心特点
    execlint execl(const char *path, const char *arg, ..., (char *)NULL);1. path 需指定可执行文件的完整路径(如 /bin/ls
    2. 参数列表可变,最后必须以 (char *)NULL 结尾(标记参数结束)
    execlpint execlp(const char *file, const char *arg, ..., (char *)NULL);1. file 可只给文件名(如 ls),会自动从 PATH 环境变量中查找可执行文件
    2. 参数列表以 (char *)NULL 结尾
    execleint execle(const char *path, const char *arg, ..., (char *)NULL, char *const envp[]);1. path 需完整路径
    2. 最后一个参数 envp 是自定义环境变量数组(替代当前进程的环境变量)
    execvint execv(const char *path, char *const argv[]);1. path 需完整路径
    2. 参数通过字符串数组 argv 传递,数组最后一个元素必须是 NULL
    execvpint execv(const char *file, char *const argv[]);1. file 可只给文件名(依赖 PATH 查找)
    2. 参数通过数组 argv 传递,末尾需 NULL
    execveint execve(const char *path, char *const argv[], char *const envp[]);1. 唯一的系统调用(其他 5 个都是库函数,最终调用 execve
    2. 支持自定义环境变量数组 envp

    http://www.dtcms.com/a/353103.html

    相关文章:

  • 基于Prometheus Pushgateway与Alertmanager的自定义指标监控与告警实践指南
  • 【HTML】隐藏滚动条但保留功能
  • 年度优质会议推荐:【西安石油大学主办|IEEE出版|往届均EI】第七届智能控制、测量与信号处理国际学术会议 (ICMSP 2025)
  • Playwright进阶指南 (6) | 自动化测试实战
  • 从 GRIT 到 WebUI:Chromium 内置资源加载与前端展示的完整链路解析
  • 寻找AI——初识墨刀AI
  • 【FPGA】VGA显示-贪吃蛇
  • oracle 表空间扩容(增加新的数据文件)
  • 浅聊达梦数据库物理热备的概念及原理
  • VESA时序检测模块设计verilog实现
  • 力扣hot100:字母异位词分组和最长连续序列(49,128)
  • Ansible的介绍+ansible平台部署
  • 互联网大厂Java面试深度解析:从基础到微服务云原生的全场景模拟
  • 公开课程 | 大规模图数据管理与分析 第二讲:图的度量、性质与生成模型
  • redbook的判断完美数
  • 销售数据分析平台
  • LeetCode hot 100 每日一题(18)——206.反转链表
  • 开源 | 推荐一套企业级开源AI人工智能训练推理平台(数算岛):完整代码包含多租户、分布式训练、模型市场、多框架支持、边缘端适配、云边协同协议:
  • 高并发写入、毫秒级查询——盘古信息携手 TDengine 时序数据库解决六大技术挑战
  • SimLab Composer8.2_win中文_3D绘画_安装教程
  • 音频时长裁剪工具:高效处理音频,让内容创作更轻松
  • 【Rust】 2. 数据类型笔记
  • Compose副作用域
  • 大模型重构建筑“能耗基因“:企业如何用物联中台打响能源革命?
  • 入行IC | 数字IC设计和FPGA哪个好?
  • STM32 入门实录:从 0 到 3 色 LED 呼吸式闪烁
  • Git-远程操作
  • 基于 Node.js 的淘宝 API 接口开发:快速构建异步数据采集服务
  • SFTP服务器可以通过同一个登录到SFTP服务器的账号密码连接上控制台吗
  • 【0420】Postgres内核 实现(借助 SMgrRelation)为指定 table(CREATE TABLE)创建 disk file