前言
上一期我们介绍了冯诺依曼体系结构以及重谈了操作系统,为进入系统的学习铺好了路!本期开始我们正式的开始学习Linux的系统部分!
本期内容介绍
什么是进程?
进程的理解
如何使用系统调用查看pid?
终止进程的两种方式
进程创建之初识fork
进程查看
什么是进程?
我们很多课本上介绍进程都是说:进程是一个正在执行的程序。内核的角度讲:进程是担任系统资源分配的实体(或进程是资源分配和调度的基本单位)
第一个课本概念有些笼统但是内核的角度目前不好理解,所以我们就先试着理解课本上的概念。根据课本上的就是正在运行的程序就是进程,那我们电脑手机打开的应用都是进程喽!我们可以打开电脑的任务管理器看看:
这么多进程操作系统总得管理吧!如何管理呢?虽然当前还没有介绍任何进程的管理方式!但是根据上一期的介绍,操作系统一定是先描述,在组织对进程做管理的!
进程的理解
我们通过上一期的冯诺依曼体系结构可以知道:任何程序在未被运行之前都是以2进制的形式存储在磁盘当中的,当运行时必须先把其对应的代码和数据加载到内存,然后被CPU执行。比如上面的有qq,微信,画图等,他们的代码和数据也都是要被加载到内存的!例如下图:
加载到内存中的代码和数据就是进程吗?
这个问题就转换成了,你如何证明你是你们学校的学生???你肯定说这有什么难的?我在学校我就是学生呀!是的,你们的保安大叔、保洁阿姨、宿管阿姨也是这样想的。他们是你们学校的学生吗?显然不是!所以上面加载到内存中的程序的代码和数据不是进程,而是进程的一部分!那你如何证明你是你们学校的学生呢?是不是你的学籍档案信息在没在你们学校的教务系统上呀!所以,要证明你是你们学校的学生,不一定非要你在校园中,而是你的信息要在教务系统中,因为教务系统是学校对学生做管理的!同理要证明你是进程,不是你的数据和代码在内存中你就是进程,而是你要在操作系统管理进程的数据结构PCB里面,你才是进程!
总结:一个可执行程序加载到内存的代码和数据不是进程,而是进程的一部分,进程应该是当前程序的PCB + 它的代码和数据!
OK,所以我们下面来介绍一下PCB!
描述进程
上一期谈到了校长对学生的管理本质上是对学生的信息的管理!通过struct 结构体对学生的信息做描述,在通过数据结构例如链表进行对学生对象做组织!所以,最后校长对学生的管理本质上就是对链表的增删查改!同理,这里的操作系统对进程的管理也是一样。先通过 struct 进程的属性进行描述,在通过数据结构对进程进行组织!最后操作系统对进程的管理也就变成了对某特定数据结构的增删查改!
在操作系统学科中(不是某一款具体的操作系统),来描述进程属性的结构体叫PCB(process control block 进程控制块)。
也就是某个进程的信息就在它对应PCB的对象里面!而我们今天研究的是Linux这一款具体的操作系统,它的PCB是task_struct。task_struct是Linux内核的一种数据结构。
task_struck的内容
上一期的描述学生的结构体有姓名、年龄等。我们也好奇描述进程的结构体有哪些内容?下面来粗略的介绍一下,后期有的会详谈例如状态、优先级等!
标识符:描述本进程的唯一表示符,用于和其他进程做区别的!和你的学号类似~!
状态:任务(进程)状态,退出代码、退出信号等
优先级:相对于其他进程的优先级。例如:哪个进程先被调度,哪个被后调度等!
程序计数器:程序中即将被执行的下一条指令的地址!
内存指针:指向当前进程的代码和数据的指针,以及和其他共享内存块的指针
上下文数据:执行进程时处理器的寄存器中的数据
I/O状态信息:包括请求I/O请求分配给进程的I/O设备和被进程使用的文件列表!
组织进程
Linux内核中是以task_struct双链表的形式组织的!
总结
加载到内存中的可执行的代码和数据不是进程!进程 = PCB + 它的代码和数据!
为什么要有PCB?因为操作系统要对进程进行管理!如何管理?先描述(task_struct),在组织(某数据结构)
如何理解进程的动态运行?
我们上面也看到了我们的进程是有很多的!一个进程一会被执行、一会不被执行。例如:你一会刷抖音、一会有切换到微信聊天了!这个过程中你的进程是如何运行的呢?但是很简单,你的进程是动态的切换状态运行的!我们知道所有的进程都是要被CPU执行的,CPU只有一个,所以就得切换的执行!例如你在刷抖音时,微信就在等待CPU,当你切换到微信时抖音在等待CPU!这个过程其实在操作系统中叫排队!这里的排队是只在特定的队列中等待资源,例如就绪队列、运行队列、阻塞队列等。
这里有个问题是:进程的在各种队列中排队等待资源是它的PCB(task_struct)在排队还是它的代码和数据在排队?
答案显而易见,是他的PCB在排队, 代码加载到内存就好了,代码和数据什么排队!这个就转换成了,你在找工作的时候,是你的简历在一面、二面、三面、HR面流动,还是你在跟着他们转?当然是你的简历在他们中流动!
所以,总结:进程的动态运行是指task_struct在不同队列中等待资源的过程!
如何使用系统调用查看pid?
在介绍前我们先来介绍一个前置的小知识!我们平时在xshell中 ./xxx 执行可执行程序时本质就是让系统创建进程并执行。而我们前面谈过系统的指令就是一个个的可执行程序(在Linux中运行大部分指令的操作的本质就是运行进程)。所以可以得到一下结论:我们写的可执行程序 == 系统命令 == 可执行文件。
我们上面在描述进程中介绍过,每个进程为了和其他进程做区别都会有一个自己的唯一标识符,叫做pid!如何查看呢?OK,我们写一个小小的代码演示一下如何查看pid:
使用指令查看进程的pid
写了这样一个简单的代码!我们线面演示一下查看pid:
OK,这里用到了一条指令:
ps ajx | head -1 && ps ajx | grep process
我们来解释一下:
ps ajx / ps ajx /ps xaj是查看系统中所有进程的
head -n是获取前n行的东西,n不指定默认前10行
| 这个是管道,就是将上一的结果给我直接处理!
grep是行文本过滤器, grep process是获取出现process的所有行
&&是在执行前面的同时有执行后面的!就是并且的意思
所以上面的意思是:获取进程信息的第一行的同时过滤出含有process的所有行!
我们发现了,果然有pid!其实pid是一个pid_t 类型的变量,pid_t实际上就是unsigned_int。
用户(进程)自己查看自己的pid
这是我们通过指令查看的,我们的进程想要查看自己的pid如何查看呢?首先可以确定的一点是:用户是无法到操作系统中去查看的!原因上期介绍了,操作系统不相信任何人!!但是还要给用户提供服务,所以他提供了系统的调用接口 --- getpid()!
进程获取自己的pid:getpid()
进程获取自己的父进程的pid:getppid()
关于父子进程,很早以前就铺垫过!在介绍xshell的运行原理时,说是王婆为了让自己的说媒的名誉一直保持好,遇到棘手的说媒时就会让实习生去!这个实习生就是王婆(父进程)的子进程。
OK,我们加上父进程的pid看看:
这里我们还需要注意的一个点是:
我们每次启动进程时对应的pid都是不一样的,这是正常的!!
我们说过pid是唯一标识一个进程的,和你的学号一样!为什么每次启动pid都会不一样的呢?举个例子,你今年高考没考好考了700分进了清华,有了自己的学号。觉得不满意就退学回家继续备战高考了,第二年你入又考了720分进了清华,又有了自己的学号!但是这两个学号是不一样的。这里的进程也是一样的,每一次启动都会给你一个新的pid来标识你!OK,我们可以验证一下:
我们看到三次启动它的pid都不一样!这样符合我们的理解!但是它的父进程的pid好像都一样哎!都是5794,这个5794是谁?其实就是我们的命令行解释器bash! 我们可以查一下:
这也和我们在xshell运行原理联系了起来!我们当前所有在命令行解释器创建的进程的父进程都是bash!
终止进程的两种方式
直接在命令行解释器那里直接按:ctrl + c
kill -9 进程的pid
这里第一种是用户层面的终止!第二种是直接把进程给杀掉!至于第二种为什么这样写这里不做解释,到后面的信号那里会在谈的~!
进程创建之初识fork
什么是fork函数?
fork是一个操作系统提供的函数接口,和普通函数调用一样,作用是创建子进程,注意fork之后的代码是父子进程共享的!
pid_t fork(void);
他有一个pid_t类型的返回值,如果创建成功返回父进程子进程的pid,返回子进程0,否则返回-1!也即是说,如果创建成功fork会返回两个值!这个暂时这里解释不清楚,涉及虚拟地址空间的问题!后面会再谈的~!这里目前记住就好了!
OK,我们先来写一个最简单的创建子进程的代码看看:
果然我们看到了先是父进程打印了第一句,然后fork创建了子进程,而fork父子进程代码共享,所以他们分别打印了hello world!没问题!我们继续思考,创建一个进程的本质是在操作系统中多了一个进程,即多了一个PCB(task_struct)和它自己的代码和数据!我们知道父进程的代码和数据是从磁盘加载到内存的,那子进程的代码和数据是哪来的呢??这里直接说结论:代码是继承父进程的,但是数据不是!这个这里也不再介绍了,后面的写时拷贝在介绍~!
为什么要创建子进程?
这个问题其实很简单!创建子进程的目的是为了让父子进程执行不一样的代码!如果一样,就不用子进程了,父进程一个就OK了~!所以,我们再来写一份简单的代码演示一下创建进程!
果然我们看到了前三秒时一个进程,后面是两个进程同时跑的!关于右边,我这里用到了shell的一些简单的语法写了一个监控脚本!如下:作用是,每隔一秒检查一下指定的进程!
while :; do ps ajx | head -1 && ps ajx | grep process | grep -v grep; sleep 1; done
对fork两次返回的简单理解
这份代码如果你详细的想一下,就目前而言,估计你的三观要崩塌了~!一个值怎么既可以大于0又可以等于0呢???一开始介绍进程创建的时候说:fork会有两个返回值!?一个函数怎么有两个返回值??因为截止目前,我们以前写的所有代码都是单进程的,而fork是创建子进程的,也就是fork后不再是单进程了而是多进程了!我们知道fork是系统提供的函数,在fork内部已经是父子进程创建当返回时创建完成了,即此时已经是两个进程了返回各自返回各自的!所以,fork返回值就是既可以大于0又可以等于0了!
进程查看
我们所有的进程都在/proc这个目录下面。
比如说,你要查看某个进程,例如1号进程,你就要到/proc/1里面去看:
这里我们想要查看我们运行起来的进程该如何查看呢?也是一样的,我们在/proc/xxx(我们的进程pid)即可查到:
果然是可以查到的!我们注意到,他这里有cwd和exe,cwd是current work dir即当前进程的工作目录,exe是链接文件,即当前的进程,上面也很清楚,第一个子进程的是lesson7/process,第二个父进程就是bash喽~!这里的cwd就想起了一个C语言时候的知识点!介绍文件的时候当时说如果这个文件不存在以w的形式打开的话就会自动在当前的路径下创建!当时不知道你有没有理解这个当前路径?我当时也只是记住了,并理解为什么他不存在会在当前的路径下创建!学了这里我才明白了,当前路径是当前进程的工作路径也就是cwd(每个进程启动会记录自己的工作路径),所以如果一个文件不存在还是以w的方式打开的话就会在cwd/xxx创建~!
OK,我们可以演示一下:
我们先新建一个目录:
看一下有没有在当前路径下创建一个log.txt:
没问题!但是我现在不想创建在当前路径下了,我想创建到其他的地方,该如何做呢??这里就介绍一下另一个函数:chdir(path);改变创建的创建文件的路径,path就是要创建文件的路径!
假设我们创建在,/home/cp/oslearn下面:
没有问题!进程的概念就介绍到这里了!
OK,好兄弟,本期分享就到这里,下期我们再见!
结束语:要成功必须要有充足的信心和足够的耐心!