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

Linux进程详细解析

1.操作系统

概念

任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:

 • 内核(进程管理,内存管理,文件管理,驱动管理)

• 其他程序(例如函数库,shell程序等等)

设计操作系统的目的 

• 对下,与硬件交互,管理所有的软硬件资源

• 对上,为用户程序(应用程序)提供⼀个良好的执行环境

核心功能

  1. 进程管理:负责进程的创建、调度、同步、通信及销毁,合理分配 CPU 资源,确保多任务环境下各进程高效运行,提升系统整体效率。
  2. 内存管理:动态分配与回收内存空间,通过虚拟内存技术扩展内存能力,保障进程间地址空间相互独立,避免干扰,提高内存利用率。
  3. 文件系统管理:对文件的创建、删除、读写、权限控制等进行组织与管理,确保数据存储的安全性、一致性,方便用户及应用程序对文件的操作与访问。
  4. 驱动管理:协调与控制外部设备(如输入 / 输出设备、存储设备等),实现设备驱动程序的加载与运行,保障设备与系统的兼容性,提升设备使用效率。

如何理解"管理"

操作系统是怎么管理内部的文件呢?是直接管理资源还是通过一系列的接口来管理资源呢?

资源就像学生一样,学生有着年龄,专业,性别等等的属性,系统内的文件也有着许多属性,如大小,所在路径等等。

在学校里面,我们一般一整个学期都见不到几次校长,所以对于我们来说,是辅导员在直接管理我们,而不是校长直接管理我们。所以管理者和被管理者之间可以不同见面

那校长是怎么间接管理我们的呢?校长可以通过辅导员来获取学生的数据来管理我们,并不用平时亲自管理学生。所以管理者和被管理者可以通过数据进行管理,由于是通过辅导员来获取数据,所以可以由中间层来获取数据

学生的数据就如同数据结构中结点的数据一样。可以将学生的数据输入计算机的链表当中,将日常与学生的管理工作转化为对计算机中链表的管理工作

那么在计算机中,我们可以这样子来解释操作系统的管理过程,操作系统对数据进行建模,转化成数据结构,管理就变成了对数据结构的管理。

所以操作系统的管理重点是:先描述,再组织

总结:计算机管理硬件

1. 描述起来,用struct结构体

2. 组织起来,用链表或其他高效的数据结构

系统调用和库函数概念

• 在开发角度,操作系统对外会表现为⼀个整体,但是会暴露自己的部分接口,供上层开发使用, 这部分由操作系统提供的接口,叫做系统调用。

• 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部 分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行而次开 发。

那么我们为什么需要系统调用呢?

我们可以通过银行的管理模式来解释系统调用,我们在存钱取钱的时候,我们并不是我们自己进入银行金库中自己存钱取钱的,而是通过银行的窗口与工作人员进行交流再进行相应的操作,为什么需要与工作人员进行交流呢,因为银行不相信其他人,只能通过工作人员进行存钱取钱操作,害怕其他人做出出格的事情

系统调用也是如此,操作系统就是银行,系统调用就是工作人员,而我们就是其他人,操作系统不相信我们,我们无法直接与底层资源进行互动,害怕我们操作失误损坏数据,只能允许我们通过系统调用与底层资源进行互动

进程

基本概念与基本操作

• 课本概念:程序的⼀个执行实例,正在执行的程序等

• 内核观点:担当分配系统资源(CPU时间,内存)的实体。

描述进程-PCB

基本概念

• 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

• 课本上称之为PCB(process control block),Linux操作系统下的PCB是:task_struct 。task_struct-PCB的⼀种

• 在Linux中描述进程的结构体叫做task_struct。 

• task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

•进程=PCB+自己的代码和数据

进程的大致结构 

struct xxx
{int a;int b;......struct xxx* next;struct mm_struct * node;
}

task_struct的数据结构就类似上面那段代码,只不过struct里面存放的是更加详细的进程信息,然后task_struct通过next指针链接起来形成一个链表。

那么怎么查找正在运行的代码和数据呢?task_struct中存在一个结构体指针struct mm_struct *,如同上面的那段代码相似,然后可以通过node指针找到正在运行的代码和数据。

所以进程的所有信息都可以通过task_struct间接或直接找到,所以对进程的管理就变成了对链表的增删查改

组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里

查看进程 

我们可以通过ls /proc来查看此时正在运行的进程。

这些数字就是进程号

通过系统调用获取进程标示符 

• 进程id(PID)

• 父进程id(PPID)

我们可以通过两个函数来查看pid和ppid,分别是getpid()和getppid()。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());return 0;
}

当我们运行上面那段代码后就会显示出我们的进程号和父进程号 

此时我们可能会疑惑,为什么我们的代码为什么还会有父进程,代码并不是通过其他代码来调用的,我们来查看父进程的具体信息

我们可以看出父进程是bash开始运行的,bash就是我们的命令行

我们输入的命令实际上都是以字符串的形式给bash,前面的那段字符是bash打印出来的。后面的绿色光标就是bash等待我们的scanf,所以就停下来了

我们可以通过ll -1 /proc/进程号来查看进程的exe和cwd

我们可x以看到文件后面跟着一串路径,其中cwd是记录文件的所在路径,exe就是文件的实际路径

那这个cwd有什么作用呢?在使用c语言的fopen时不带路径只带文件名,就会在当前目录下自动创建文件,但是这是怎么判断当前目录的,这是由于进程启动时,cwd就会自动记录当前所在路径,程序会读取cwd路径来创建文件

但是我们可以通过一些代码来改变cwd

chdir("/root/a");

此时我们的cwd就会被修改成/root/a

创建子进程

初始fork

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret < 0){perror("fork");return 1;}else if(ret == 0){ //childprintf("I am child : %d!, ret: %d\n", getpid(), ret);}else{             //fatherprintf("I am father : %d!, ret: %d\n", getpid(), ret);}sleep(1);return 0;
}

我们可以通过这段代码来帮助我们了解fork函数和子进程,代码的运行结果如下

这时候可能会疑惑,fork为什么会有两个返回值?  两个返回值各种给父子如何返回? ⼀个变量怎么能让 if 和 else if 同时成立这个问题

fork函数存在两个返回值的原因是因为fork会跟据父进程和子进程返回不同的结果,fork会给父进程返回子进程的pid,会给子进程返回0

为什么会出现不同的返回值呢:由于父进程:子进程=1:n,一个父进程可以有多个子进程,父进程需要通过不同的pid来区分不同的子进程,子进程不需要获取父进程的pid,本身就可以通过getppid来返回父进程的pid

那为什么fork会返回两次呢:我们可能会陷入一个误区,fork函数不仅会创建子进程,父进程也会运行fork函数,在fork函数内部,可能运行到一半的代码时,子进程可能已经被创建好甚至被调度了,所以在最后return语句时,不仅子进程会返回数据,父进程也会返回数据。

那为什么会输出两行内容呢,因为子进程被创建出来之后没有自己的代码和数据,只会自动执行父进程的代码,从子进程被创建出来的地方开始运行。

ret的值不同的原因涉及到了写时拷贝在子进程创建出来的时候,子进程没有自己的代码和数据,所以它只会执行父进程的数据,所以子进程会指向父进程的代码和数据,此时子进程和父进程就指向了相同的内容,如果遇到修改数据的情况呢?此时系统就会为修改数据的进程重新开辟一个新的空间,将原先的代码和数据拷贝过去,这就是写时拷贝。

进程状态

操作系统对硬件的管理

操作系统如何对硬件资源做管理,先描述,再组织。先创建结构体,将对应硬件资源的数据填写完善,将对硬件资源的管理转化成对链表的管理

操作系统的基础状态

操作系统中存在着三种基础状态:运行,阻塞和挂起。

运行状态:只要进程在调度队列中

阻塞状态:等待某种设备或资源就绪,就像C++的cin等待输入一样,此时进程仍在内存中

挂起状态:进程被暂时停止执行,资源使用被限制或部分释放,可能由用户或系统强制触发

为什么会形成阻塞:当进程未等待到某种设备就绪的时候,操作系统会将进程从运行队列上拿取下来,然后将进程的pcb链入到等待输入的设备的等待队列中,不在运行队列里,就永远不会被调度,就形成了阻塞状态,在等待设备就绪过程中,进程并不能够知道设备时候就绪,而操作系统作为硬件资源的管理者,它能够第一时间反应设备状态发生改变,然后更改设备状态,并且检查设备的等待对列,如果不为空,将等待队列的状态设为运行,并重新将进程链入运行队列中

当计算机内存资源不足的时候,设备链表上不会被调度的进程交换到磁盘上,只有PCB没有代码和数据的进程叫做阻塞挂起

操作系统内部数据结构

为什么操作系统内部会存在多个数据结构,而数据的空间只有一份,那么操作系统是怎么解决的?

操作系统中存在多个类似代码的数据结构

struct list_head
{struct list_head *next *prev;
};

将他放入task_struct中

struct task_struct
{.......struct list_head link;.......
};

 link的next和prev不会指向下一个或后一个的task_struct,而是指向下一个或后一个task_struct的link中的next或prev。

但是指向link的话我们如何去访问task_struct中的内容呢:我们可以通过类似下面代码的方式获取link的偏移量,通过将0强转成task_struct类型并访问其中的links,就可以知道结点的起始地址与links地址之间的偏移量,此时我们就可以访问结点中的任何信息了

list-&((struct task_struct*)0->link)

此时task_struct之间遍可以通过类似下图的形式将单个数据装换成多个数据结构 ,在同一个结点内可以有多个links,可以将结点通过links形成不同的数据结构,这就是为什么操作系统中存在一个结点可以被同时放在硬件的链表和运行队列中

Linux内核源代码状态

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 */
};

在这里我们可以看到,在源代码中,进程状态用类似宏定义的整数表示。

• R运行状态(running):并不意味着进程⼀定在运行中,它表明进程要么是在运行中要么在运行 队列⾥。

• S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。

• D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个 状态的进程通常会等待IO的结束。

• T停止状态(stopped): 可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的 进程可以通过发送SIGCONT信号让进程继续运行。

• X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表里看到这个状态。

•Z僵尸状态(zombie):子进程等待父进程回收

这里我们可以着重了解D,T,Z三种状态

D状态:当进程向磁盘中写入数据,在写入数据的过程中,进程会在原地等待磁盘返回写入结果,但是如果在等待过程中,系统资源严重不足的话,操作系统会直接将进程杀掉,但是在杀掉进程之后,磁盘写入失败想要告诉进程,但是进程不见了,磁盘只能将写入的资源丢失,丢失资源用户不知道,容易造成严重后果,后面设计出D状态给等待磁盘返回结果的进程,表示操作系统不能直接杀掉D状态进程

T:我们可能不理解在什么情况下进程是停止状态,当我们在使用cgdb的断点在停止代码运行时,此时通过查看进程状态,能够发现进程状态为T

Z僵尸状态:我们创建子进程的目的是为了帮助父进程完成某些东西,一旦子进程运行完成后,父进程就得知道子进程的运行结果如何,但是如果父进程一直不回收信息,那么子进程就会一直存在,此时子进程的状态就是僵尸状态,这种状态会导致内存泄漏的问题。

但是如果父进程退出后子进程仍未退出,此时子进程的父进程就会变成1号进程,那么1号进程是什么东西,我们可以把它看成操作系统本身,为什么要领养:如果不被领养,子进程就会进入僵尸状态,从而造成内存泄漏无法解决,需要通过系统来解决问题,但是被1号进程接管后。子进程就会变成后台进程

进程优先级

基本概念

 • cpu资源分配的先后顺序,就是指进程的优先权(priority)。

• 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性 能。

• 还可以把进程运行到指定的CPU上,这样⼀来,把不重要的进程安排到某个CPU,可以大改善 系统整体性能。

• 优先级是一种数字,值越低,优先级越高,反之越低

查看系统进程

我们可以使用ps -l命令显示当前系统中运行的进程信息

我们很容易注意到其中的几个重要信息,有下:

• UID:代表执行者的身份,在系统中并非通过用户名字来识别的拥有者,而是通过id来识别的,使用ls -ln即可查看用户对应的id

所以我们可以了解一下系统怎么知道我们访问文件的时候,是拥有者,所属组还是other?

用命令访问文件本质进程访问文件,进程在启动的时候会记录启动者的id,然后与文件的拥有者,所属组做对比,判断启动者的权限有哪些

• PID:代表这个进程的代号

• PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

• PRI:代表这个进程可被执行的优先级,其值越小越早被执行

• NI:代表这个进程的nice值

PRI and NI 

• PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此 值越小进程的优先级别越高

• 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值

• PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=80(默认优先级)+nice。

• 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快 被执行

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

• nice其取值范围是-20到19,⼀共40个级别。为了保证公平,nice才会存在一定的范围,变化的幅度不会太大,所以优先级的范围在[60,99]

• PRI的默认值为80。

查看进程优先级的命令

可以通过top,nice,renice等命令来修改

概念-竞争、独立、并行、并发

• 竞争性:系统进程数目众多,而CPU资源只有少量,甚至只有1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

• 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

• 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行

• 并发:多个进程在⼀个CPU下采用进程切换的方式,在⼀段时间之内,让多个进程都得以推进,称 之为并发

进程切换

CPU上下文切换:

其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下⼀个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下⼀个任务的运行,这⼀过程就是context switch。

1.寄存器

寄存器就是cpu内部的临时空间

寄存器是一份空间,而寄存器内的数据只是内容,可以随时替换,所以寄存器!=寄存器里面的数据

2.切换进程时的文件去哪了

task_struct中还存在着一个结构体TSS,文件会保存到TSS中

Linux2.6内核进程O(1)调度队列

我们通过这张图片来了解一下进程切换的大致内容

图片中存在队列:queue[140],他表示着140个优先级,其中0-99是实时优先级,100-139才是我们可以操作的优先级,在相应的优先级后面,queue[优先级]会存放着我们将要运行的进程,queue可以理解为一个hash表。

我们可以看到图片中有存在相同的变量我们可以将它理解为一个结构体数组array,分为array[0]和array[1],array[0]是活跃进程,array[1]是过期进程。图片中有两个指针,active指向array[0],expired指向array[1]。

bitmap[5]是通过位图操作来减少queue查找进程的时间复杂度,可以迅速的查找1的位置来锁定接下来要运行的进程,为什么大小是5呢?原因很简单,4过小,5过大。

现在有几个问题

1.遇到死循环进程的话后面的进程还会运行吗?

我们的操作系统采用的是分时操作系统,当死循环程序的时间片用完之后,操作系统就会将死循环进程从active中抓取出来,放到expired中

2.expired中的程序什么时候才会运行?

expired中的进程一般是刚开始的进程和过期进程,只有等待active里面的进程运行完成后,swap(active,expired),然后运行进程

3当我们新来一个进程后,是要放在expired中,与过期进程存放在一起?

这就是进程的就绪状态,但是随着硬件的更新,出来了内核优先级抢占问题,它能够直接将新进程直接插入到active中。

相关文章:

  • Day14(链表)——LeetCode234.回文链表141.环形链表
  • MySQL:13.用户管理
  • 【漫话机器学习系列】226.测试集、训练集、验证集(test,training,validation sets)
  • 天线设计实战:三大经典布局的摆放逻辑与核心技术要点!
  • el-input限制输入只能是数字 限制input只能输入数字
  • 力扣hot100,739每日温度(单调栈)详解
  • 什么是模块化区块链?Polkadot 架构解析
  • 【今日三题】笨小猴(模拟) / 主持人调度(排序) / 分割等和子集(01背包)
  • Pinia——Vue的Store状态管理库
  • 【KWDB创作者计划】_企业级多模数据库实战:用KWDB实现时序+关系数据毫秒级融合(附代码、性能优化与架构图)
  • 基于深度学习的智能交通流量监控与预测系统设计与实现
  • Spring Boot API版本控制实践指南
  • 基于深度学习的医疗诊断辅助系统设计
  • 深入详解人工智能数学基础—概率论-KL散度在变分自编码器(VAE)中的应用
  • SHCTF-REVERSE
  • 【极致版】华为云Astro轻应用抽取IoTDA影子设备参数生成表格页面全流程
  • 如何在 iPhone 上恢复已删除的联系人:简短指南
  • OkHttp源码梳理
  • 2025 FIC wp
  • 【C语言】fprintf与perror对比,两种报错提示的方法
  • 2025全国知识产权宣传周:用AI生成的图片要小心什么?
  • 海关总署牵头部署开展跨境贸易便利化专项行动
  • “70后”女博士张姿卸任国家国防科技工业局副局长
  • 韩国检方起诉前总统文在寅
  • 【社论】上海车展40年,见证了什么
  • 亚振家居半年内第二次筹划变更控制权:控股股东正与收购方商谈交易核心条款