Linux系统 —— 进程系列 - 进程的概念,PCB与PID和fork

目录

[1. 进程的基本概念与基本操作](#1. 进程的基本概念与基本操作)

[1.1 总结:什么才是进程](#1.1 总结:什么才是进程)

[2. 描述进程-PCB(process control block)](#2. 描述进程-PCB(process control block))

[2.1 task_ struct](#2.1 task_ struct)

[2.2 task_ struct内容分类](#2.2 task_ struct内容分类)

[2.3 组织进程](#2.3 组织进程)

[3. PID](#3. PID)

[获取当前进程PID - getpid](#获取当前进程PID - getpid)

[获取父进程PID - getppid](#获取父进程PID - getppid)

如何查看进程

[1 进程的信息可以通过 /proc 系统⽂件夹查看](#1 进程的信息可以通过 /proc 系统⽂件夹查看)

[2 我们还可以使⽤top和ps这些⼯具来获取](#2 我们还可以使⽤top和ps这些⼯具来获取)

[4. fork - 创建子进程](#4. fork - 创建子进程)

[4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?](#4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?)

[4.2 fork函数为什么会返回两次?](#4.2 fork函数为什么会返回两次?)

总结:


1. 进程的基本概念与基本操作

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

内核观点:担当分配系统资源(CPU时间,内存)的实体
但是,上面的内容对于我们初学者来说是非常难以理解的,所以我们可以画图来进行理解
一个程序运行起来叫做进程,未运行起来就是一个二进制文件,存储在磁盘当中,程序运行之前要加载到内存,在操作上就是(./cmd) ,而真正意义当我们执行时cmd就会加载到内存当中去

这里我们是把一个程序加载到内存,有没有可能我们在操作系统里,在同一时刻我们可以把成百上千给程序加载到内存里呢?答案是一定的

而有一款软件是最开始就加载进来了,叫做操作系统

如上图,我们可以看到在内存中,有很多加载的程序,那么我们就有一个问题:这些程序在内存中的什么位置被加载?代码和数据是否已经被cpu已经执行完毕呢?有没有可能其中的程序需要暂停运行?有没有可能其中内存不够了,需要扩容呢?

所以在内存中有这么多加载的内存,它们都需要申请内存和加载内存,所以操作系统要对这些进程,这些代码进行管理,但是我们可以做管理吗?答案是不能,因为操作系统虽然知道里面有代码,但是操作系统并不知道是那个进程

但是我们有没有解决方法呢?答案是先描述,再组织

操作系统会在操作系统内部(内核)给每一个代码和数据构建一个struct结构体,然后每加载进来一个程序,操作系统就为该程序创建一个同类型的对象,然后把当前这个进程按照创造的对象填写好信息,填好之后就有了一个对应的节点,而这里每一个节点都有对应的指针可以指向对应的代码和数据

与此同时,我们对应的每个节点当中,它的指针还可以指向它的下一个节点,最终在操作系统内我们形成了一个程序列表,我们把这个程序列表叫做进程列表

1.1 总结:什么才是进程

​​​​​​


2. 描述进程-PCB(process control block)

其实我们上面已经见过PCB了,就是我们上面创建的结构体,在操作系统中所有表示进程的都叫PCB,而这个结构体叫做进程控制块

进程控制块里包含进程的所有属性,进程的所有属性都可以直接或者间接通过 task_ struct找到

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

++所以为什么一个进程加载的时候操作系统要给它创建一个对应的PCB即task_ struct对象呢?++

因为操作系统要管理进程,所以要先描述,再组织,所以必须要有描述进程的task_ struct再组织管理成数据结构,操作系统就会转化成对应数据结构的增删查改

操作系统要对进程做管理,那么就必须要对进程的PCB做管理


2.1 task_ struct

Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园https://www.cnblogs.com/tongyan2/p/5544887.html

  1. 在Linux中描述进程的结构体叫做task_struct

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


2.2 task_ struct 内容分类

1. 标示符: 描述本进程的唯⼀标⽰符,用来和其他进程进行区别

2. 状态: 任务状态,退出代码,退出信号等

3. 优先级: 相对于其他进程的优先级

4. 程序计数器: 程序中即将被执⾏的下⼀条指令的地址

5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

6. 上下文数据: 进程执⾏时处理器的寄存器中的数据休学例⼦,要加图CPU,寄存器

7. I/O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表

8. 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等

9. 其他信息


2.3 组织进程

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

在linux内核中, 最基本的组织进程task_struct的方式, 是采用双向链表进行组织的


3. PID

获取当前进程PID - getpid

进程创建的时候, 里面都有一个自己的PID,我们如何在一个程序运行时获取这个程序的PID呢?

如图所示,操作系统里面上层是系统调用, 下层是内存缓冲区, 这个时候内存中已经缓存了两个进程,PCB对象里面含有PID,我们可以使用ps axj和管道来获取进程的PID

但是我们都知道,操作系统不相信我们用户, 所以我们就不能直接访问PCB(task_struct)也就是里面的PID, 状态等, 想要获取这些字段就必须使用系统调用接口

如果我们想要获取当前进程的PID的系统调用接口的话,我们就要使用getpid(), 这个函数在哪个进程里被调用, 就会返回哪个进程的PID

注意:pid是一个整形, 下面是我们自己定义的一个获取系统调用接口的程序

当前进程为:


获取父进程PID - getppid

对上面的概念进行试验之后, 我们再来看一下父进程, 也就是PPID

我们发现,父进程ppid都是不改变的,而pid每次都是变化的,这是为什么呢?

我们联想到王婆的例子

王婆不想给自己的牌子砸了,所以就找了些实习生去解决,不需要王婆亲自去解决了,派实习生去解决,所以我们的-bash就是王婆(父进程)

1.运行一个进程时,系统会自动创建bash进程

2.命令行再执行所有的程序或者指令时,它所对应的进程,所对应的父进程就是bash本身。我们自己执行的程序或者指令都是bash进程的子进程

3.执行出问题的时候,只会是子进程出问题,不会影响bash进程

4.我们启动xshell时候,系统自动生成bash进程,显示命令行

如何查看进程

1 进程的信息可以通过 /proc 系统⽂件夹查看

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

proc文件夹里面的目录都是临时文件,当进程开始就会创建一个以这个进程的pid作为名字的文件夹,进程结束的时候就会删除这个文件夹
我们发现: 这些进程都是目录, 并且这些目录的名字都是数字

我们可以通过proc文件来查看这个进程更详细的信息


2 我们还可以使⽤top和ps这些⼯具来获取

ps:

top:

在命令行中,执行命令/执行程序,本质是bash的进程,创建子进程,来执行我们的代码


4. fork - 创建子进程

fork是一个系统调用,fork没有参数,有两个返回值

父子进程代码共享,数据各⾃开辟空间,私有⼀份(采用写时拷贝)

fork函数的本质就是是一个系统调用

上面那张图意思就是说如果fork函数成功了, 那么给父进程返回子进程的pid, 0返回给子进程。 如果失败了, 就返回 -1 给父进程。 并且没有子进程被创建

也就是说, fork有两个返回值, 并且这两个返回值的类型都是pid_t, 也就是有符号整形

运行结果:

我们可以看到每一秒打印一条父进程, 打印一条子进程,这说明父进程和子进程是同时进行的,并且id > 0, 和 id == 0同时成立, 如果在其他的代码中, 这两种情况不可能同时存在,但是在调用的fork下就可以

所以在我们fork之后所有的代码都是共享的,只不过父进程认为自己>0,子进程认为自己=0,所以父进程会进入第三个,子进程会进入第二个,所以我们就可以做到父子执行不同的代码块


到了这里,我们有几个问题


4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?

在我们的操作系统里,我们的父进程比上子进程是1:N的,简单来说就是任何一个父进程可以有一个或者多个子进程

所以我们在创建子进程时,一定要把子进程的pid返回给父进程,因为父进程需要通过返回的不同的pid来区分不同的子进程,而子进程不需要获得父进程的pid,因为子进程已经能够获得getppid了,所以子进程只需要表明自己成功建立就可以了


4.2 fork函数为什么会返回两次?

在这个问题之前我们先讨论另一个问题:一个函数执行到return时,函数的核心功能做完了吗?答案是核心功能已经完成了

fork函数的本质就是是一个系统调用

如图所示:fork函数创建子进程后, 函数后面的代码就会被子进程和父进程所共享

当fork函数里面创建好子进程后(绿色方框部分) 子进程就被创建出来了, 然后执行流就变成了两个(可以使用if或者else或者else if来进行分流), 一个子进程的执行流, 一个父进程的执行流

也就是说, 这个时候的return语句, 其实就是由两个执行流会执行它,因为return也是语句,所以会被执行两次,而这两个执行流都会返回一个值,所以这就是为什么fork会有两个返回值, 并且返回值给一个给子进程, 一个给父进程的原因


总结:

1. 进程具有独立性,简单来说就是一个进程挂掉了并不会影响其他进程,哪怕是父进程挂掉了也不会影响子进程

如果子进程和父进程公用一个数据块, 当子进程改变数据的时候, 父进程也会改变数据,所以 不能让父进程和子进程共享一份数据

对于子进程来说数据是独立的,所以当创建子进程的时候要拷贝一份父进程的数据独立出来

这个时候父进程崩溃或者子进程崩溃都不会影响对方


未完待续~

相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室11 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕14 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫15 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1117 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯19 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983419 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器