Linux进程概念1

前言

从这篇博客开始,后面我们主要讲xshell中Linux内容,C++后续会继续补充一点
Linux基础内容我就不讲了,直接从进程开始讲

1. 进程是什么

在这里插入图片描述
在这里插入图片描述

像这种程序正在运行,还没有结束的过程就是一个进程,进程在电脑底部就是.exe文件中的代码拷贝到了内存中,原本是在磁盘中的,再加上创建了一个task_struct的结构体(记录进程的数据)
如何证明有这个进程呢,我们再打开一个xshell就可以找到了
在这里插入图片描述

ps ajx就是查找所有的进程,|是管道,head -1就是拿出第一行
用分号就可以在一行输入多条命令,grep就是打出含有myproc的进程
可以看出第一个就是我们运行程序的进程
第二个是因为我们使用的这个指令也是进程,打印的这个过程grep的这个过程也是进程,所以会打印出来
在这里插入图片描述
&&和分号也是同样的效果
入股不想打印出grep的进程,可以这样
在这里插入图片描述
这个就是不查找grep的意思,这个我们可以看出每一个指令在运行中也是指令,因为指令执行很快,所以我们一般看不到进程

2. 进程的pid

每个进程都有pid,这个就相当于我们学生每个人都有学号一样
getid这个函数可以获取进程的pid
在这里插入图片描述
pid_t是一个长的整型,是分装好的整型
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看出,进程在运行中,是不会改变pid的,进程结束,下一次开启进程,pid会改变
在这里插入图片描述
进程在运行中是可以杀死的
在这里插入图片描述
kill -9 加pid就可以杀死对应进程了
在这里插入图片描述
当然也可以用ctrl+c
在这里插入图片描述
查看进程相关数据,除了ps,还可以去看根目录下的proc目录,这个目录记录了所有的进程
在这里插入图片描述
对应进程的文件名就是pid
在这里插入图片描述
这其中有两个比较重要的数据,就是
在这里插入图片描述
exe就是我们运行程序.exe也就是myproc的位置
如果我们在运行中删除可执行文件的话
在这里插入图片描述
在这里插入图片描述
这里的exe就会变成这样,因为它不在那个位置了嘛
在这里插入图片描述
但是程序仍然可以运行,这是因为程序是导入了内存中的,当然结束运行,下一次肯定就不能运行了
下面讲一下CWD的作用,CWD的作用就是当前的工作目录,也就是我们fopen创建文件默认的工作路径,就是本目录
在这里插入图片描述
在这里插入图片描述
但我们可以用chdir来改变CWD,来改变当前工作目录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CWD是改变了,但是根目录下却是test.txt文件,为什么呢,这是因为我们在更目录下没有建立文件的权限,没有w权限
换个目录就可以了
在这里插入图片描述
在这里插入图片描述
因为没有访问权限,所以P==NULL
换一个有权限的目录就可以了,比如ck
第三个我要讲的是/proc
这个目录不是磁盘级别的目录,因为这个要实时更新,必须要更新很快,而且经常更新,所以这个是内存级别的文件,只有运行的时候才会创立文件

3. ppid

ppid就是这个进程的父进程的pid,每个进程的创建都必须由父进程创建
getppid这个函数就可以获得父进程的pid
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
查一下我们就知道了,可执行程序的父进程都是bash
而且bash的pid是不会改变的
这里的bash是一个命令行解释器,就相当于xshell的外壳shell,就是linux操作系统的外壳程序shell
所以说可执行程序,命令的父进程都是bash,他们的进程都要由bash创造
一般来说,打开一个xshell就有一个bash
在这里插入图片描述
我们这里又打开了一个xshell,所以就有了两个bash

4. fork

fork的主要作用就是创建一个子进程
程序运行本身就是一个进程,然后fork就可以以这个进程在创建一个子进程
在这里插入图片描述
在这里插入图片描述
可以看出,5191就是父进程,3688就是bash,因为我这次是第二天打开的xshell,所以bash的pid不一样了
然后创建了子进程5192,所以子进程就会从创建的时候开始执行后面的代码,子进程代码的执行和父进程是一样的,执行的是相同的代码,然后他们是同时执行的
写(void)id是为了防止id没有使用而报错

这个就相当于从fork开始就开始分叉了,执行两个代码,同时执行两个相同的代码
在这里插入图片描述

在这里插入图片描述
fork是一个函数,是一个系统调用函数,如果创建成功,就会给父进程返回一个子进程的pid,给子进程返回一个0
为什么一个函数会返回两个值呢,因为fork内部函数,如果走到了return,说明就已经创建成功了,说明至少到这,就已经开始分叉了,分叉就是执行的代码是一样的,但是代码的数据不一样,比如子进程的id就是0,父进程的id就是子进程的pid
在这里插入图片描述
在这里插入图片描述
看这个就可以看出,子进程和父进程的数据都是各自是各自的
然后子进程和父进程的代码执行基本上是同时的
就是在一秒钟内,执行了父进程一步,就去看看子进程能不能执行,能的话就去执行
在这里插入图片描述
而且去查看也是两个进程
子进程也会有一个task_struct,但是子父进程的task_struct都是指向同一个内存中的代码,代码共享,数据各自一份
子父进程之间具有独立性,互不干扰
在这里插入图片描述
在vim的底行模式中,这样写就可以把文本中的所有1替换成2
在这里插入图片描述

接下来我们来编写c++程序
C++后缀为.cpp或者.cc
在这里插入图片描述
c++这样写还不行,因为待会我们要写的C++有范围for,这个是要C++11才支持的,所以还要改一下
在这里插入图片描述
这样写的话就支持C++11了,但我的是支持C++11的,所以不用写
在这里插入图片描述

在这里插入图片描述
这里再说一下,子进程的task_struct是拷贝父进程的task_struct,因为两个很相似,就是数据不一样而已,所以拷贝就拷贝呗
至于父子进程的运行逻辑是怎么样的,现在暂时不考虑

fork之后,父子进程到底谁先执行这个取自于OS(操作系统)的一个叫做调度器的东西

5. 操作系统进程状态

5.1 并行和并发

操作系统执行代码的时候,不是要把一个进程执行完毕,再去执行另一个进程,而是给每一个进程都分配一个时间(时间片),执行了这个时间片,再去执行下一个进程。。。。。。这个我们说的是CPU单核的情况下
这个就是并行,,,就是这个进程执行了之后,下次执行的时候就是另一个轮回了,所以每个继承都是比较公平的

而并发就是,多个CPU执行多个进程

5.2 时间片

时间片就是每个进程执行的时间,这个时间是很短的,所以在一段时间内,就可以执行很多个继承,在我们看来,就是同时进行的,而且不卡顿

像那种每个进程挨着挨着执行,比较公平的,就是分时操作系统
但是比如在汽车这个操作系统中,刹车这个进程的优先级就比较高,遇到危险的时候就会马上执行,所以这种叫做实时操作系统

5.3 状态

在这里插入图片描述
上面这个图就是操作系统的状态
下面我们来讲
在这里插入图片描述

CPU将一个task_struct拿到CPU中执行和在排队的过程就叫做就绪和执行过程,这两个过程是没有什么区别的,也就是运行过程
大的区域是内存,小的区域就是操作系统,操作系统在内存中
task_struct拿到CPU这个过程叫做调度
在这里插入图片描述
对硬件的管理也是一个队列
当我们的程序中有scanf的时候,task_struct就会拼接到对应的键盘struct_device后面,等待输入数据,输入了数据,task_struct就又会拼接到原来的task_struct队列后面,拼接到struct_device后面这个过程叫做阻塞
所以运行和阻塞的本质就是让不同的进程处于不同的队列中
在这里插入图片描述
当内存空间不足的时候,那么阻塞的时候,把exe文件放在内存中就有点浪费空间了,操作系统就会把exe这个文件放在磁盘中的一个swap分区中,等要用的时候,就会把这个拿出来,拿进磁盘叫做换出,拿出磁盘叫做换入,这样明显效率低,所以一般企业会禁止这个swap,而且内存没有空间了,就该更新了,,,,这个拿去swap的过程就叫做挂起,合起来叫做阻塞挂起,,,这个过程就是时间换空间
这里再补充一点task_struct也叫做PCB

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

这个就是Linux中的状态
S是睡眠状态,R是运行状态
在这里插入图片描述
注意这里要写\n,不然的话,缓冲区无法刷新,就无法打印
在这里插入图片描述
在这里插入图片描述
这里我们可以看出,code程序的状态就是S,+我们暂时不管,S状态就是睡眠状态,为什么不是运行状态呢,因为printf是访问外设的,是去访问显示器的,所以它的task_struct就会转移到struct device,,这个状态不是运行状态,而是阻塞状态,所以说阻塞状态就是休眠状态,而非printf的代码就是运行状态了,因为访问外设的时间远大于运行的时间,所以我们看到的大多是S状态了
在这里插入图片描述
在这里插入图片描述

我们干掉printf和sleep,因为一个访问外设,一个就是真的休眠了,这样就会一直执行代码,就是运行状态了
除了printf,scanf也是一种休眠状态

D是一种深度睡眠状态,与前面的区别是什么呢,区别就是,前面的睡眠是可中断的睡眠,这个睡眠是不可中断的睡眠
什么是可中断的呢,就是你ctrl+c或者kill一下,它就真的停止运行了
而D就是kill -9也不管用
为什么这样设定呢
比如说,我们要把一堆数据,这一堆数据是很多数据,传入到磁盘中的时候,因为磁盘很慢嘛,如果我们此时终止掉程序,那么就会出现数据丢失的情况,所以我们设定D状态,让用户和操作系统都不能杀死这个程序,操作系统为什么要杀死这个程序呢,因为当内存不足的时候,操作系统就要杀死一些内存占用大的进程,所以D状态就是一种不可中断的睡眠
接下来我们来讲一下T状态,T状态就是停止状态
如果产生停止状态呢,我们只需要kill -19就可以了
在这里插入图片描述

在这里插入图片描述

就这样,就暂停了
在这里插入图片描述
kill -18就可以取消暂停状态了
在这里插入图片描述
程序就可以继续运行了
但是我们注意到这次S状态后面就没有+了,为什么呢
在这里插入图片描述
而且我们在程序运行过程中还可以执行代码
在这里插入图片描述
而且ctrl+c也无法停止,为什么呢
因为程序分为前台程序和后台程序
前台程序,我们不可以用指令,或者用了也没用,可以ctrl+c终止
暂停之后再变回来,就变为了后台程序,后台程序的话,就不能ctrl+c结束了,而且可以执行指令,只能kill-9来杀掉,这个就相当于我们在更新游戏的时候,是可以退出来的,在后台更新
至于t状态嘛,这个是跟踪暂停,这个要在调试的时候才看得到
在这里插入图片描述
这样写makefile的话,可执行程序就会增加调试信息,占用空间就会增大
在这里插入图片描述
我们这里写个脚本,这样可以一直调用ps
在这里插入图片描述

在这里插入图片描述
看这个我们就知道了,运行到断点的时候就是t状态了
在这里插入图片描述
但有些时候也是S状态,因为跳到另一个断点的过程就是运行的状态了,就不是t状态了
所以打到断点的时候就是状态t
如果被用户或者操作系统停止的话(非法但是不致命的错误),就是T

6.1main函数的返回值

接下来我们再来看一个来判断上一个函数是否正常结束的操作
在这里插入图片描述
这里我们可以看出,echo $?可以判断出上一步的指令是否正常结束
0就是正常结束
非0就是不正常结束
为什么会这样呢,这个是因为指令是用C语言写的,最后也会return 0;
只要return 0就说明是正常结束
return 其他数字就不是正常结束
所以说echo $?的这个数字就是return的数字
在这里插入图片描述
在这里插入图片描述

6.2 X与Z状态

Z状态就是僵尸状态,X状态就是死亡状态
为什么要处于僵尸状态呢,因为人在死后,法医还要看人是怎么死的,判断完后,再把人埋了,程序结束后,操作系统还要对程序进行一些处理,最后才删除
X状态就是死亡状态,这个状态一旦发生就会马上被回收处理,所以我们一般是看不到的,所以不做研究
首先讲一下,进程为什么会被创建,被创建是为了完成用户的任务的,进程结束,任务完成了吗,这个就要在僵尸状态看了,主要就是在僵尸状态看这个进程到底任务完成了没有
是要告诉父进程和OS任务到死完成了没有,然后就可以处理删除这个僵尸状态了,主要是父进程来处理僵尸
进程就是等于内核数据结构(task_struct)+代码和数据
进程退出会有退出码,这个码就是return回来的的数字,这个是会保存在task_struct中,进程退出,进入僵尸状态,代码和数据就没有用了,我们就可以看task_struct,来看任务到底完成了没有
如何查看僵尸状态呢,我们可以这样,主要是父进程处理僵尸,如果父进程什么都不干,就在那里划水,那么子进程就会一直处于僵尸状态,但是如果父进程是一个很勤奋的进程,那么一进入僵尸状态,然后就会马上处理,马上搞死,所以我们就看不到,所以我们就要设计一个懒的父进程或者很忙的父进程,让他来不及处理僵尸,这样我们就可以看到了

6.3 僵尸状态

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
可以看出子进程由S状态变为了Z状态,这个是因为父进程一直在工作,就算子进程死了,父进程也不会处理它
这个就是僵尸状态了
在这里插入图片描述

或者在子进程还没有自己死亡的时候,就杀死它,这也是个僵尸状态
处于僵尸状态的话,就会一直占用内存,也不会处理,至于父进程最终要怎么处理,这个我们要后面才说
或者我们用户怎么处理,这个要后面才说,要用一个叫做wait的东西

6.4 父进程先死

在这里插入图片描述
在这里插入图片描述
看这个我们先杀死父进程,我们就看不到父进程的僵尸状态,为什么呢,因为父进程的父进程是bash,bash是一个勤劳的进程,一旦僵尸了,就会马上处理死亡信息,然后马上宣判死刑,我们就看不到僵尸了
这时候我们还发现子进程的父进程都变成了进程1,
在这里插入图片描述
在这里插入图片描述
输入top我们就知道了,进程1就是systemd,这个是系统的进程,意思就是换爸爸了
这个爸爸也是比较勤劳的,
在这里插入图片描述
然后我们还可以看出,子进程换爸爸之后,自己就变成了后台进程
这个进程也叫做孤儿进程,因为是被领养的爸爸

7. 进程优先级

7.1 用户id

在这里插入图片描述
在讲优先级之前我们先讲用户ID
用户ID就是UID这个东西,就是1000
也就是我们ck用户的id就是1000
在这里插入图片描述
如上我们ls -ln就可以看到用户id

7.2 优先级基本概念

PRI就是优先级,NI是优先级的变化数据,也是修正数据,nice数据
意思就是,一个进程的新的优先级就是老的优先级+nice
为什么要有NI呢,这是因为万一程序在运行过程中修改优先级,就不知道怎么处理了
优先级的数字越小就代表优先级越高
下面我们来介绍一种修改优先级的方式,当然修改优先级有指令和代码两种方式,这里我们只介绍指令的方式
在这里插入图片描述
这里我们可以看出,我们程序的优先级是80,NI是0
怎么修改呢
输入top
然后r
然后输入要修改的进程的pid20626
然后输入NI
我们这里输入的是100
在这里插入图片描述
最后变成了这个,说明NI最大为19
而且值的注意的就是,一般修改了一次就不允许修改第二次了
要想修改的话,就要重新来一次了
或者我们直接sudo就可以了
再来一次我们发现NI最小是-20
而且每次修改,都会把PRI重置成80,再来计算,不会无限+,无限-的
这样做是为了把优先级控制在一个区间,防止优先级太高了,比一些必须得进程优先级还要高了就不行了
-20到19总共40个数字,为什么是40个呢,我们后面再说

7.3 进程切换

在这里插入图片描述
看这个图我们知道,程序是怎么运行的呢
首先CPU中有一个指针来控制task_struct来运行
CPU不是直接把exe文件中的汇编代码拿进来的,而是通过寄存器
pc寄存器保存当前代码的下一条代码的地址
ir来保存当前执行的代码
eax,ebx用来存值
就这样,代码就可以执行下去了

7.4 进程调度

但是,如果又有一个代码,task_struct2,又因为进程是会调度的,而且还因为寄存器只有一套,所以原来保存的数据,肯定会被覆盖掉,那么就完了
怎么解决呢
os会把这些数据,就是寄存器中的数据,也就是上下文数据,都放在内存中,就是自己所对应的task_struct中,这样就可以调度了,而且会接着上次的代码继续执行
在这里插入图片描述
在这里插入图片描述
看这个我们就知道了,task_struct中果然可以储存寄存器中的数据
但是CPU到底是怎么调度的呢,不可能真的是挨着挨着一个一个调度吧,如果这样的话,那优先级有什么用呢,所以肯定有特定的调度算法的,这样挨着挨着掉,这个是最老的cpu调度了

总结

下一篇还是接着这个讲,从CPU的调度算法开始讲

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值