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

Linux系统编程01:进程概念(万字图文解析)

01-进程概念(万字图文解析)

一、进程的概念

1、从用户角度,进程是程序的一次动态执行过程。
2、从内核角度,进程是具有独立功能的程序在一个数据集上执行的过程,它是系统进行资源分配和调度的一个独立单位。

二、进程(实体)的组成

1、程序执行的过程

在这里插入图片描述

  • 引入进程实体的概念后,可把进程定义为:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。

2、进程(实体)的组成

一个进程实体(进程映像)由PCB、程序段、数据段组成。进程是动态的,进程实体(进程映像)是静态的。进程实体反应了进程在某一时刻的状态(如:x++后,x=2)

1)、数据段
  • 程序的代码
2)、代码段
  • 进程运行过程中产生的各种数据(如:程序中定义的变量)
3)、进程控制块task_struct
  • PCB是进程存在的唯一标志,当进程被创建时,操作系统为其创建进程控制块(Process Control Block, PCB),当进程结束时,会回收其PCB。
  • 在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

task_struct主要分为四个模块:

  1. 进程描述信息

    • 进程标识符PID
    • 用户标识符UID
    • 父进程标识符PPID
  2. 进程控制和管理信息

    • CPU、磁盘、网络流量使用情况统计
    • 进程当前状态:运行(TASK_RUNNING)、可中断睡眠(TASK_INTERRUPTIBLE)、僵尸(TASK_ZOMBIE)等,通过state字段表示
    • 优先级:影响调度顺序
    • 状态: 任务状态,退出代码,退出信号等。
  3. 资源分配清单

    • 文件描述符表:记录进程打开的文件,通过files字段管理
    • 内存指针:包括代码段、数据段、堆栈的指针,以及共享内存区域的引用
    • I/O设备:记录分配给进程的设备及I/O请求状态
  4. 处理机相关信息

    • 寄存器上下文:保存进程被切换时的CPU寄存器值,用于恢复执行
    • 程序计数器(PC):指向下一条待执行指令的地址
    • 调度信息:如调度策略(policy)、时间片(time_slice)等,关联到调度器。

总结:操作系统对进程进行管理工作所需的信息都存在PCB中

3、查看进程

在Linux操作系统中,进程的信息可以通过 /proc系统文件夹查看。

如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。

在这里插入图片描述

也可以使用top,ps等用户级命令获取系统进程信息。

  • top

在这里插入图片描述

  • ps
ps -ajx | head -1 && ps -ajx | grep 进程名 | grep -v grep

4、通过系统调用获取进程标识符

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); // 总是成功:返回当前进程的pid
pid_t getppid(void); // 总是成功:返回父进程的pid
#include<stdio.h>
#include<unistd.h>
int main()
{for(;;){printf("pid:%d\n", getpid());printf("ppid:%d\n", getppid());sleep(1);}return 0;
}
[shenalex@VM-8-16-centos 03-Process]$ ./a.out 
pid:7666
ppid:32052
pid:7666
ppid:32052
^C

三、进程的组织

所有运行在系统里的进程都以task_struct链表的形式存在内核里

四、进程的特征

  • 动态性:进程是程序的执行过程,动态产生、变化和消亡。这是进程最基本的特征
  • 并发性:多个进程实体能在一段时间内同时运行
  • 独立性:进程是独立运行的基本单位,是系统资源分配的基本单位
  • 异步性:各进程按各自独立的、不可预知的速度向前推进
  • 结构性:每个进程都有对应的进程控制块(PCB)来描述其状态和资源占用情况

五、进程的状态与转换

1、Linux内核源码中定义

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
  • 运行状态(Running/Runnable - R):表示进程正在CPU上执行或位于运行队列中等待调度执行。即使进程没有实际占用CPU,只要它处于可被调度的状态(即在运行队列中),也会被标记为R状态。
  • 可中断睡眠状态(Interruptible Sleep - S):进程因等待某些事件(如I/O操作完成、信号量释放等)而暂停执行,但可以被信号或事件唤醒。这是最常见的睡眠状态,用户空间的进程等待通常处于此状态。
  • 不可中断睡眠状态(Uninterruptible Sleep - D):或者叫磁盘休眠状态,进程因等待某些内核操作(通常是I/O操作)而暂停执行,在此状态下进程不会响应信号。这种状态通常持续时间很短,但如果硬件出现问题可能导致进程长时间处于D状态。
  • 停止状态(Stopped/Traced - T):发送SIGSTOP信号可以停止进程,直到收到继续信号(如SIGCONT)才会恢复运行。
  • 僵尸状态(Zombie - Z):进程已终止执行,但其退出状态尚未被父进程读取(通过wait()系统调用)。此时进程的PCB仍然保留,直到父进程获取其终止信息。
  • 死亡状态(Dead - X):进程完全终止的瞬时状态,通常不会在进程列表中看到,因为系统会立即回收相关资源。

3、进程状态转换

在这里插入图片描述

4、Z(zombie)-僵尸进程

  • 僵尸进程(zombie):进程死亡时,内核会将进程的执行信息 (如: pid, 终止状态, 资源利用等数据) 保留到进程的 PCB 中,方便该进程的父进程以后查看;并给父进程发送SIGCHLD信号。子进程的PCB不会销毁,子进程的pid也不会被回收。这样的进程,我们称之为僵尸进程。

注:默认情况下,父进程会忽略 SIGCHLD 信号

  • 僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。
  • 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

僵尸进程危害:

  • 进程的退出状态必须被维持下去,因为父进程需要读取该进程的信息。但是如果父进程一直不读取,那子进程将一直处于僵尸状态
  • 维护退出状态属于进程基本信息,保存在task_struct(PCB)中,也就是说,如果进程一直处于Z状态不退出,PCB将一直存在。
  • 一个父进程创建了很多子进程,却不回收,将造成内存资源的浪费,因为数据结构对象本身就要占用内存。

5、孤儿进程

  • 孤儿进程 (orphan):如果父进程先于子进程死亡,那么子进程会成为孤儿进程。孤儿进程会被 init 进程 (1号进程) 收养, init 进程的核心职责就是调用wait()等待各个孤儿进程。
  • 如果父进程没有执行 wait() 就终止了,那么 init 进程将收养子进程并调用 wait() 从系统中移除僵尸进程。
  • 如果父进程的生命周期很长 (如网络服务器),请一定要调用 wait() 函数,以确保系统总能及时清理那些死去的子进程。否则,内核的进程表中将为该子进程长时间保留一条记录;如果存在大量的此类僵尸进程,它们势必将填满内核进程表,从而导致无法创建新进程。

六、进程优先级

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

查看系统进程

用ps -l命令可以查看与当前终端关联的进程

[shenalex@VM-8-16-centos 03-Process]$ ps -l
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 R  1001 27778 32052  0  80   0 - 38324 -      pts/0    00:00:00 ps
0 S  1001 32052 25095  0  80   0 - 29334 do_wai pts/0    00:00:00 bash

UID : 代表执行者的身份
PID : 进程的代号
PPID :父进程的代号
PRI :进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值

PRI与NI

  • PRI:进程的优先级,此值越小,进程的优先级别越高。
  • NI:nice值,表示进程可被执行的优先级的修正数值。它不是进程的优先级,但是nice值会影响到进程的优先级变化。nice其取值范围是-20至19,一共40个级别
  • PRI值越小越快被执行,加入nice值后,则新的PRI将变为:PRI(new) = PRI(old) + nice
  • 也就是说nice值为负值的时候,该进程优先级值将变小,即其优先级会变高,越快被执行

所以,在Linux下,调整进程优先级,就是调整进程nice值

top命令更改已存在进程的nice

进入top后按“r”–>输入进程PID–>输入nice值

在这里插入图片描述

七、环境变量

1、概念

环境变量(Environment Variables)是操作系统和应用程序用来存储配置信息、路径设置和运行时参数的动态键值对。它们存在于所有Shell会话中,直接影响命令行为、程序执行和系统功能。如:编写的C/C++程序在链接阶段,环境变量可以帮助编译器找到动静态库的位置,最终生成可执行程序。

2、作用范围

  • 全局变量(系统级):对所有用户和进程生效(如 PATH、HOME)。
  • 局部变量(用户/会话级):仅对当前 shell 或用户进程生效(如自定义临时变量)。

3、常见环境变量

  • PATH:定义可执行文件的搜索路径,用冒号 : 分隔。
  • HOME:当前用户的家目录路径(如 /home/username)。
  • SHELL:默认使用的 shell 路径(如 /bin/bash)。

4、本地变量与环境变量的操作

1)查看变量
  • 查看所有环境变量:
env
  • 查看特定变量(如:PATH):
echo $PATH
  • 显示本地定义的shell变量和环境变量
set
2)设置变量
  • 临时设置(仅当前 shell 有效):
export MY_VAR="value"  # 环境变量(子进程可以继承)
LOCAL_VAR="value"      # 本地变量(仅当前shell进程有效,子进程无法继承!!!!)
  • 永久生效:
    • 对当前用户:将 export 命令添加到 ~/.bashrc~/.bash_profile 文件末尾。
    • 对所有用户:添加到 /etc/environment/etc/profile(需管理员权限)。
3)删除变量
unset MY_VAR
4)更新PATH
export PATH=$PATH:/new/path  # 追加新路径,比如想添加可执行程序a.out,只需要将所在目录添加进去即可

5、环境变量的组织方式

  • 每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串。

在这里插入图片描述

6、通过代码获取环境变量

  • 通过命令行第三个参数获取环境
#include <stdio.h>
int main(int argc, char *argv[], char *env[]) // 通过命令行第三个参数获取环境变量
{for (int i = 0; env[i]; i++)printf("%s\n", env[i]);return 0;
}
  • 通过第三方变量environ获取
#include <stdio.h>// 通过第三方变量environ获取
int main()
{// libc中定义的全局变量environ指向环境变量表,// 由于environ没有包含在任何头文件中, 所以在使用时要用extern声明。extern char **environ;for (int i = 0; environ[i]; i++)printf("%s\n", environ[i]);return 0;
}
  • getenv()和putenv()库函数
#include <stdio.h>
#include <stdlib.h>
int main()
{// 1.通过库函数getenv获取环境变量printf("%s\n", getenv("PATH"));// 2.putenv设置环境变量,使用"NAME=VALUE"格式,成功返回0putenv("MY_TEST=12345");printf("设置了环境变量:MY_TEST=%s\n", getenv("MY_TEST"));// 3.putenv删除环境变量putenv("MY_TEST");printf("删除了环境变量MY_TEST:%s\n", getenv("MY_TEST"));return 0;
}
[shenalex@VM-8-16-centos 03-Process]$ ./a.out 
/home/shenalex/.vscode-server/bin/8b3775030ed1a69b13e4f4c628c612102e30a681/bin/remote-cli:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/shenalex/.local/bin:/home/shenalex/bin:/home/shenalex/Test/code/LinuxOSProgramming/03-Process
设置了环境变量:MY_TEST=12345
删除了环境变量MY_TEST:(null)

7、环境变量测试

1)PATH变量测试
  • 为什么有些命令可以直接执行,不需要带路径,而我们自己写的程序需要带路径才能执行?(例如:ls和./a.out)
    • 这是因为诸如ls此类的命令已经保存在了系统环境变量PATH中,当你在终端输入一个命令(如 ls、grep),系统会按PATH中的顺序搜索匹配的可执行文件,找到后执行。

  • 如果自己的程序加入环境变量PATH当中, export PATH=$PATH:/home/shenalex/Test/code/LinuxOSProgramming/03-Process,也可以不带路径直接执行a.out:
[shenalex@VM-8-16-centos 03-Process]$ pwd
/home/shenalex/Test/code/LinuxOSProgramming/03-Process
[shenalex@VM-8-16-centos 03-Process]$ ll
total 24
-rw-rw-r-- 1 shenalex shenalex  185 Aug 10 20:16 01-getpid.c
-rwxrwxr-x 1 shenalex shenalex 8616 Aug 10 22:51 a.out
-rw-rw-r-- 1 shenalex shenalex   71 Aug 10 19:49 makefile
-rw-rw-r-- 1 shenalex shenalex  438 Aug 10 22:00 test.c
[shenalex@VM-8-16-centos 03-Process]$ export PATH=$PATH:/home/shenalex/Test/code/LinuxOSProgramming/03-Process
[shenalex@VM-8-16-centos 03-Process]$ a.out
Hello world!
2)HOME变量测试
  • 环境HOME指向当前用户的家目录,对于root用户则是/root。
[root@VM-8-16-centos 03-Process]# echo $HOME
/root

和HOME都指向当前用户家目录,但是不是环境变量,而是 Shell 的一个特殊字符,在命令行中会被自动扩展为当前用户的家目录路径

3)环境变量全局属性
  • 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{char *env = getenv("TEST");if (env)printf("%s\n", env);elseprintf("null\n");return 0;
}

直接执行发现不存在该环境变量:

[shenalex@VM-8-16-centos 03-Process]$ ./a.out 
null

如果我们导入该环境变量后再执行程序,发现有结果了:

[shenalex@VM-8-16-centos 03-Process]$ export TEST=123
[shenalex@VM-8-16-centos 03-Process]$ ./a.out 
123

这是因为当前直接在终端运行的进程是shell(bash)的子进程,当shell通过export导入TEST环境变量后,由于具有全局性,子进程可以继承父进程环境变量,则可以查看输出。

八、进程地址空间

1、进程的内存映象

在这里插入图片描述

2、逻辑地址与物理地址转换

使用取地址操作符得到的是逻辑地址,每个进程都有自己的一套逻辑地址,但是逻辑地址需要映射到物理内存地址区。因此,两个不同的进程使用数值相同的逻辑地址映射到不同的物理内存地址是非常正常的现象。

逻辑地址与物理地址的转换依赖页表和MMU,前者为每个进程独享,保存在内核空间中,后者集成在CPU内,是实现逻辑地址与物理地址转换必不可缺的硬件设备。页表指明了逻辑地址与物理地址转换的规则,相当于“目录”,具体实现交由CPU硬件MMU实现。为了缓和CPU与内存速度矛盾,引入了快表的高速缓存,其将部分常用的页表项保存在其中,使得CPU无需每次都从内存中读取页表项数据,大大提高了效率。

在这里插入图片描述

参考资料

  1. (美)William Stallings著, 陈向群, 陈渝译. (2020). 操作系统(第9版). 北京: 电子工业出版社
  2. 汤小丹, 王红玲, 姜华, 汤子瀛. (2021). 计算机操作系统(第1版). 北京: 人民邮电出版社
  3. (美)Robert Love著, 陈莉君, 康华译. (2011). Linux内核设计与实现(原书第3版)(M). 北京: 机械工业出版社
  4. 王道计算机教育. (2019). 操作系统课程[B站视频]. Bilibili. https://www.bilibili.com/video/BV1YE411D7nH?p=2&vd_source=33ca4a4964785165eb751135aadccddd
http://www.dtcms.com/a/470025.html

相关文章:

  • 前端通用AI rules定义,适用于Cursor ,Trae,Qorder等AI开发工具
  • Go 协程在实际项目中的应用详解
  • 最简单的做网站南沙滩网站建设
  • Hive 知识点梳理
  • MySQL常见报错分析及解决方案总结(15)---Can’t connect to MySQL server on ‘localhost‘ (10061)
  • 网站上做的vi设计是怎么做的互联网设计公司排名
  • jetson orin nane 编译 paddle
  • 兰州网站卡法百度网页收录
  • [1-02-05].第04章:Win工具
  • 软件需求规格说明书(SRS)标准模板与编写指南——含功能需求、非功能需求、接口设计与验收标准
  • VS 2022 中创建一个最小的 Django 项目
  • 建设网站的功能定位是什么原因网站建设模版
  • 网站建设教程书籍免费下载网站是公司域名是个人可以吗
  • 编译原理机测客观题(3)自顶向下语法分析练习题
  • [学习日记][springboot 1-7][leetcode 6道]
  • 双榜加冕!赛博威入选第一新声AI Agent厂商图谱与AI产业创新先锋榜单
  • YOLO 目标检测算法全解析:原理、分类与性能指标
  • 华为5736交换机 dhcp静态绑定方法
  • 网站建设要求 优帮云合肥seo网站管理
  • LeetCode 3186.施咒的最大总伤害:动态规划+双指针——O(1)空间(暂未发现其他O(1)空间的题解)
  • LeetCode 热题 100(持续更新版)
  • 网站开发 jsp开发工具网页设计介绍说明
  • 沈阳网站建站推广湖南网站建设效果
  • 会员体系搭建攻略讲解:从分层运营到提升用户忠诚度
  • Merkle Tree(默克尔树)原理分析
  • Vue3 学习笔记 8:其它 API
  • 库早报|15999元!先临三维发布口袋式3D扫描仪;激光制造与增材制造大会延期;拓竹双项入选《时代》年度发明榜
  • 流量网站建设教程电子商务网站建设php
  • React中Element、Fiber、createElement和Component关系
  • 大语言模型(LLM)是“预制菜”? 从应用到底层原理,在到中央厨房的深度解析