进程(上)【Linux操作系统】

文章目录

进程是什么?

进程=内核数据结构+程序的代码和数据

(代码和数据中的数据包括这个可执行程序运行前/运行时产生的所有数据,比如:堆区数据,栈区数据等)

所以
进程才等于运行起来的程序

注意:
进程创建的时候,是先创建它对应的内核数据结构[PCB]再加载它的代码和数据

就像考进一个学校,再拿到录取通知书之前你的信息就已经被学校记录在学生管理系统里面了,所以在你人进入学校之前,就有了你对应的数据对象了

而进程退出是则是反过来,先释放代码和数据,再处理PCB
因为进程退出后,就不会再执行代码了,所以代码和数据没有存在的必要了

但是由于要在PCB里面存储退出信息,给父进程/操作系统读取,所以PCB还不能直接释放


操作系统管理进程

操作系统管理进程也是遵循:
先描述,再组织的原则的

进程(程序)被加载到内存之前

操作系统内部为进程设计了结构体[名字一般是task_struct],把进程的相关属性写进结构体(注意:进程(程序)的代码和数据并不会写进结构体,结构体里面只存储了指向这些数据存储位置的指针)

然后再把这些结构体变量放进了一个数据结构中

然后封装这个数据结构形成了进程列表
操作系统对进程列表进行增删查改,即可管理所有进程


进程相关的知识点

查看进程的详细属性的方法

目录:proc

是根目录下的一个文件,proc目录下有一些以数字命名的目录
那些数字就是进程的pid,而目录里面存储的就是与pid对应的进程的所有属性

也就是当进程启动时,操作系统就会在proc目录下创建一个以它的pid为名的目录,把它的所有属性存储在里面

当进程结束的时候,对应的目录也会立刻删除

proc是存储在内存中的目录,是内存级文件,不存储在磁盘中


指令:ps ajx
查看当前Linux正在运行的所有进程,以及它们的简单属性

ps ajx底层就是对目录/proc的信息的提炼和分析


Linux中的新创建的进程,都是由它的父进程创建的

在Linux的命令行中因执行指令/可执行程序而产生的进程,它们的父进程都是Linux的命令行解释器bash

只要用户登陆Linux,Linux就会创建出bash进程

Linux的进程之间的关系是树型结构
也就是一对多,一个进程只有一个父进程,但是可以有多个子进程

一般子进程的task_struct是:

①先从父类那里拷贝过来一份

②再根据子进程自己的特点进行一定调整


task_struct里面的字段简介

  1. pid:唯一标识一个进程

  2. exe:记录该进程是由磁盘中的那个可执行文件生成的,记录了它的绝对路径

    例:

    进程A是被test生成的,那么exe存储的就是test的绝对路径(/home/xyc/dir/test)

  3. cwd:当前进程在那条路径下运行
    一般是继承父进程的cwd

    进程在哪条路径下启动,那么它的cwd最开始就是那条路径

    我在路径/home/xyc这条路径下启动了进程test

    那么test最开始的cwd就是/hone/xyc
    而且如果test运行过程中不会切换到其他路径运行,那么cwd就一直是/hone/xyc

    所以进程的cwd和pwd打印出来的路径一般是相同的

    这也是为什么在一个源文件里面新建一个文件如果不指定路径,就会在当前目录下新建的原因

    因为新建时如果不指定路径或者是相对路径,它就会把cwd中存储的路径拼在前面,形成绝对路径

  4. ppid:自己这个进程的父进程的pid

  5. uid:记录该进程属于哪个用户

    作用:
    判断该用户指派给进程的任务,有没有完成/执行的权限

    这个进程的任务是删除文件,那么进程执行这个任务之前,就会通过uid与文件属性中的id(或者所属组id等)进行比较

    就知道这个用户是拥有者,所属组了


进程相关指令和函数

指令:ps ajx

查看当前Linux正在运行的所有进程,以及它们的简单属性

ps ajx底层就是对目录/proc的信息的提炼和分析


指令:ps -al

查看当前Linux正在进行的进程

的优先级的相关信息


函数 getpid()和getppid()

头文件:unistd.hsys/types.h

返回值都是:pid_t

作用:获取当前这个程序加载到内存之后生成进程的pid,ppid

pid_t本质就是封装了一下的无符号整型


函数:getcwd

参数表:gercwd(char* buf,size_t size)

头文件:unistd.h

返回值:char*

成功就返回buf

失败就返回null

参数

①char* buf:获取到的cwd放入的字符数组

②size_t size:字符数组最多能放多少个字符

作用:
编写进程源代码时使用,用来获取当前进程的PCB里面存储的cwd


指令:kill -9 进程的pid

杀死对应的pid的进程


指令:kill -19 进程的pid

暂停对应的pid的进程


指令:kill -18 进程的pid

让暂停的进程继续运行


函数chdir

头文件:unistd.h

返回值:int(0就是修改成功,-1就修改失败)

参数:const char*(文件路径)

作用:调用这个函数的进程的PCB中的cwd中存储的路径


函数:fork

头文件:unistd.h

返回值:pid_t

①如果创建子进程成功

1.就把子进程的pid给父进程(因为父进程可能会有多个孩子,为了方便管理就得知道所有孩子的pid

2.返回0给子进程(因为子进程一定只有一个父进程

②如果创建失败,就返回-1

参数:无参

作用:以当前这个源文件生成的进程为父进程创建一个子进程

注意

如果源文件里只有一个fork

fork执行之后,就会多出一个进程

而这两个进程在fork之后的代码是共享的,但是数据是私有的(也就是一些变量的值不同,修改也不影响其他进程)
所以fork之后的代码就是会被两个进程同时执行


为什么父子进程代码是共享的,但是数据是私有的?

代码共享的原因:

子进程是在父进程加载到内存,并且运行到fork之后才被创建的,所以它根本没有其他代码可以用

并且进程只需要读代码就可以运行了,代码是只读的,任何一个进程不可能做到把代码给改了

数据私有的原因:
为了保证进程之间的独立性[保证一个进程挂了不会影响另一个,多个进程进程运行时,互不影响],要做到这一点就不可能让数据共享

因为运行进程基本都会修改数据,如果数据共享,那么子进程改了a变量,父进程去访问的时候访问到的就是改了的

这样两个进程之间就会纠缠不清,不可能具有独立性


并行和并发

并发:

概念:
CPU执行进程的代码时,并不是把一个进程的代码执行完之后,才执行其他进程的代码

而是,给每一个进程预留出一定的时间(也就是时间片[这个时间一般都非常短])

这个进程在CPU上执行了预留的时间之后,就直接把它从CPU上剥离下来

再把下一个进程的代码放上去,如此循环

(也可以抽象的理解成,把一个CPU按块分成了几个小CPU,每一个小CPU执行一个进程的代码)

多个进程在一个CPU上循环切换使多个进程的代码在一个时间段内,同时得以运行推进即称并发


并行:

概念
多个进程的代码分别,同时在对应的多个CPU上执行,即称为并行


时间片

时间片就是上面提到的并发中的,操作系统给每一个进程的预留执行时间

用完了这个预留时间之后,就必须被从CPU上剥离下来

一般的民用操作系统都是分时操作系统

所谓分时,就是操作系统会给每个进程一个时间片,统一采用并发的方式执行多个进程

为什么民用操作系统基本都是分时操作系统呢?

这是因为民用的操作系统的进程一般没有特别明显的优先级

不是一定要执行完某个进程的代码,才能执行下一个进程的代码

所以调度进程时主要追求公平,也就是每个进程都跑一跑


等待的本质

每一个CPU都拥有一个运行队列[runqueue]

运行队列里面会存储一些基本信息,并且会使用一个数据结构把要调用的所有进程的task_struct保存起来形成一个调度队列

操作系统进行进程调用的时候,就直接从那个数据结构里面拿进程的代码进行分时执行


进程的运行状态是指什么?

其实只要进程的task_struct在调度队列中,这个进程就是运行状态

因为此时这个进程随时都可以被CPU调度


阻塞状态

进程如果没有再运行队列中,并且进程没有暂停或者死亡,进程的代码没被执行,那么进程就处于阻塞状态

以硬件的等待队列为例:
操作系统要管理底层的硬件,就必须要先描述再组织

所以操作系统定义了一个叫devices的结构体来描述所有硬件的特点,再实例化出结构体对象

然后再用一个数据结构把对应的结构体对象管理起来

而devices里面其实也有一个队列,叫waitqueue(即等待队列)

所以每个结构体对象都有等待队列,即每个硬件都有等待队列

如果进程需要从硬件获取数据才能继续运行代码(比如运行到了scanf())

就从需要把该进程的PCB从运行队列上拿下来

然后,让该进程的PCB去对应的硬件的等待队列中排队
而在硬件的等待队列中的进程的状态就是阻塞状态

为什么叫阻塞?

因为只有CPU能运行代码,其他的硬件不行,所以在等待队列中的进程的代码是不会运行的

外部表现就是进程卡住了

等这个进程获取到了硬件数据之后,就又会回到调度队列中排队

其实不光硬件的等待队列
进程的PCB处于任何软硬件等待队列中,都是阻塞

比如

父进程wite阻塞等待子进程,管道读端阻塞等待写端写数据

这些都是阻塞


所以运行和阻塞的本质是:
让同一个进程的PCB,处于不同的队列中(CPU的调度队列,硬件的等待队列等)

综上:
操作系统对进程的调度管理,其实就是对硬件的等待队列和CPU的调度队列的增删查改


挂起状态

内存严重不足时,操作系统为了腾出更多内存保护自己[因为操作系统也是软件,也需要使用内存]

就会把处于阻塞状态的进程加载到内存的代码和数据,换出到磁盘的swap分区中,腾出内存

[因为阻塞状态的进程不会执行代码,所以暂时不需要代码和数据]
此时这个进程就称为处于挂起状态(或者阻塞挂起状态)

当这个进程从硬件中获取了数据之后,在进入CPU的调度队列之前

再把它的代码和数据从磁盘的swap分区中换入内存


进程的代码和数据换出和换入做了很多操作,并是内存与外设之间的交互

所以其实会消耗不小的时间,所以挂起是以时间换空间的操作

所以大部分公司为了追求效率,其实会关闭swap分区,即禁止操作系统进行换入换出

如果关闭了swap分区,或者换入换出了还是内存不够,操作系统的压力是在太大就会直接把一些进程杀掉,反映给用户的就是软件闪退了

相关推荐
kyle~1 小时前
linux根目录
linux·服务器
QuiteCoder1 小时前
【Linux】软硬连接与动静态库
linux·运维·服务器
꧁༺朝花夕逝༻꧂1 小时前
Linux基础--用户管理
linux·运维
酥暮沐3 小时前
K8S 集群搭建——cri-dockerd版
linux·容器·kubernetes
美好的事情总会发生3 小时前
SDIO(Secure Digital Input Output)详解
linux·嵌入式硬件·硬件工程
琪琪花4 小时前
sshfs 将远程服务器上的文件系统挂载到本地目录
linux·运维·服务器
dreamczf4 小时前
基于Linux系统的边缘智能终端(RK3568+EtherCAT+PCIe+4G+5G)
linux·人工智能·物联网·5g
钡铼技术物联网关4 小时前
导轨式ARM工业控制器:组态软件平台的“神经中枢”
linux·数据库·人工智能·安全·智慧城市
若云止水6 小时前
Ubuntu 下 nginx-1.24.0 源码分析 - cycle->modules[i]->ctx
linux·nginx·ubuntu