目录
粗略的进程管理
由上一篇文章,我们知道了计算机内的所有管理思想都是先描述,再组织。所以,进程管理也不例外。就是一个结构体内部放入了进程的属性,再用链表连接起来
进程
进程及进程管理的概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。这一点等到线程部分再讲解。
假设我们现在有一个源文件,code.c,编译后生成了一个可执行程序myexe,可执行程序myexe是磁盘上的一个文件,要运行myexe时,先要将myexe从磁盘加载到内存,这个过程是由操作系统的加载器完成的,而操作系统也是一个软件,并且是从开机就一直运行着的,所以操作系统也已经被加载到内存了。
实际上,当myexe被加载到内存上时,它就已经是一个进程了。前面我们说了操作系统主要的四大功能中就有进程管理,既然myexe已经是进程,那么操作系统就要对它进行管理:操作系统要将其放到CPU上,称为调度,若myexe被CPU执行时,到scanf,但一直没有输入,操作系统就会进行进程切换,将其从CPU上拿下来,放到某个等待位等,未来进程执行完毕,操作系统还要将进程占用的空间释放掉。这一系列从加载到调度,从调度到切换,从切换到运行,从运行到阻塞,从阻塞到死亡,从死亡到回收的整条生命线,就称为进程管理
操作系统为了进行进程管理,就需要先对进程进行描述。进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。在windows下,这个数据结构称为PCB,linux下,这个数据结构称为task_struct
struct tast_struct
{
int pid; // 进程id
int status; // 进程状态
int prio; // 进程优先级
void *memptr; // 代码和数据的地址
上下文;
struct tast_struct* next;
};
进程可能会有很多个,所以需要给每一个进程分配一个id值,memptr是一个指针,通过它可以找到这个进程对应的代码和数据。所有进程弄成一个链表后,这个链表也称为任务列表。要执行一个进程,到任务列表中找到对应的ip值,通过这个ip值所在结点的memptr,找到对应的代码和数据,将代码和数据的起始地址放到CPU对应的寄存器上,就完成了调度。所以,当操作系统将一个可执行程序加载到内存时,不仅仅只是将可执行程序加载到内存,还要使用一个结构体来保存进程的属性,并放入任务列表中。这样,对进程的管理就变成了对任务列表的增删查改
由这句话:当操作系统将一个可执行程序加载到内存时,不仅仅只是将可执行程序加载到内存,还要使用一个结构体来保存进程的属性,并放入任务列表中,可以得出进程的概念
进程 = 内核数据结构(task_struct) + 程序的代码和数据
就像一个人是XX大学的学生,不能说这个人在大学里面就是这个大学的学生,而是应该这个人的信息在大学的学生管理系统内才能是这个大学的学生
进程是被调度的
举个例子,我们在向公司投递简历时,简历就是人的属性的集合,这就是先描述,HR将简历根据某一种顺序整理好,就是再组织,将整理好的简历给面试官看的过程就是简历在排队的过程,排队时是属性在排队,简历对应的人是没有进行排队的。当开始面试,面试官根据简历上的电话号码找到对应的人,面试完通过,再将简历交给二面的面试官,面试完没有通过,将简历扔到垃圾桶。接下来在后序面试中,面试官还会不断拿到那份简历。在这个过程中,简历就是task_struct,面试者就是可执行程序,在这个过程中,简历不断在HR、一面面试官、二面面试官等中间来回切换。操作系统会不断地调度进程,从CPU上拿上来、放下去、再拿上来、再放下去(这个过程是通过调度器完成的)。所以,进程从最先加载,到执行完毕,呈现出动态被调度的过程。所以常说进程是运行起来的程序。
task_ struct内容分类
前面我们已经了解到了将一个程序加载到内存中,可执行程序要占用一部分内存,同时还要创建一个结构体对象,如task_struct,用它来管理进程。注意:创建tast_struct是在内存上创建的,是一个纯内存级的操作,与磁盘上的文件没有关系,如关机,再开机,进程就没有了。
我们现在来看一下task_struct中都有一些什么东西呢?
我们现在在Linux上创建一个程序,并将其编译形成可执行程序,运行起来形成进程
我们make后,形成了可执行程序myproc,./myproc让其运行起来后,运行起来的程序就是进程。若想要查看这个进程,可再打开一个终端。
ps ajx是显示系统中所有启动的任务,也就是所有的进程
ps ajx | grep myproc是显示含有myproc字段的进程
ps ajx | head -1是显示ps ajx结果的第1行,也就是进程信息的名称
命令行是执行一条语句,若想让命令行执行多条语句,可使用;,也可用&&代替
现在我们就可以看到myproc这个进程,还有进程的信息的名称。那下面的grep是什么呢?
一直有grep...是因为像ps、head、grep这些命令能够罗列进程或过滤掉某些进程信息,这些命令本身也会变成进程,而执行到grep时,ps、head这两个命令已经执行完了,当用grep查myproc时,grep自己也是一个进程,且含有myproc字段,所以就会将自己过滤出来
若不想显示grep...,可反向匹配,让含有grep字段的进程不显示
所以,像ls、pwd等也是创建进程。
把程序运行起来,双击/./xxx.exe本质就是在系统中启动了一个进程。进程有两种,一种是执行完立刻就退出,像ls、pwd等指令,另一种是一直不退,直到用户退出,也称为常驻进程
当我们ctrl + c结束掉这个进程后,再去查进程就查不到这个进程了
进程的标示符
PID
我们刚刚看进程信息时,看到一个PID,PID就是进程标识符
我们进行了两次ctrl + c,并再次运行,观察每一次重新运行起来后,进程PID的变化。会发现,同一个程序,在不同时间运行时,PID是会发生变化的。
PID是进程标识符,其重要作用就是区分进程,若想获得自己进程的PID,可以使用getpid,getpid是一个系统调用,作用是获取进程的PID。可以使用man 2 getpid查看
返回值是pid_t,是系统的数据类型,实际上就是long,只不过被typedef了
只需getpid一次,因为只要进程没结束,PID是不会变化的
此时就可以打印出进程的PID了
若我们想结束一个进程,可以使用ctrl + c,也可用kill,kill是向指定进程发送信号,信号有很多,可以通过kill -l查看
可以使用9号信号,是杀掉一个进程
我们上面是通过ps ajx | grep myproc命令来查看myproc这个进程的属性,但这看到的属性是有限的。在Linux中,一切皆文件,Linux会将进程的属性以文件的形式存放,根目录下有一个proc目录,里面有很多以数字命名的目录,这个数字是指定进程的PID,以数字命名的目录里面就是这个数字对应的进程的属性
当有了一个新进程,就会在proc目录下创建一个新目录,进程结束后,这个数字目录就会被删除。即proc中关于进程的目录是实时更新的。
我们将myproc这个可执行程序运行起来,看看proc里对应的目录下有那些属性
我们重点来看其中的exe和cwd
exe是一个文件,记录源文件的路径。源文件是指这个进程的可执行程序是从磁盘中哪一个可执行程序加载而来的。所以,exe记录的路径就是磁盘上的那个可执行程序的路径
再启动一个终端,将磁盘上的可执行程序删除
会发现,可执行程序已经被删除了,但是进程仍然在正常运行。这是因为可执行程序已经加载到内存上了,删除的是磁盘上的可执行程序。此时再查看proc中的26275目录,会发现exe显示已经delete
接下来看cwd。在C语言中,使用fopen打开一个不存在的文件,会在当前源文件所在的路径下创建一个这个文件,为什么源文件同级路径下新建呢?
运行这个程序,会发现在当前路径多了一个log.txt
实际上,./myproc运行可执行程序时,所创建的进程会记录myproc所处的路径,并将路径记录到cwd属性中。运行到fopen时,会将cwd中存储的路径拼接到"log.txt"的前面,而cwd就是源文件所处的路径,所以就会在源文件所处路径下生成一个log.txt文件。当然,若是fopen中指定了路径,就会去指定路径下创建。
所以,我们现在对当前路径有一个更明确的定义:进程的CWD
可手动改变进程启动时所处的路径,使用chdir,是一个系统调用
谁调用这个接口,就将指定进程的工作路径改为指定字符串,返回0成功,返回1失败
运行起来后,会发现并没用改变。这是因为新建文件的过程一直是失败的,当前是普通用户。
确实是创建失败的
我们将路径修改成我们有权限创建文件的路径
此时就到我们指定的路径下了
ps和ls /proc都可以查看进程属性,常用的是ps。系统中提供给我们查看进程的接口只有/proc,所以ps命令底层实现就是打开/proc,并对里面的内容进行文本分析。注意:/proc是内存级的。关机后/proc里面就什么都没有了,开机后操作系统又会将进程放入。
PPID
PPID是父进程的id。在Linux系统中,启动之后,新创建任何进程时,都是由自己的父进程创建的。进程是由操作系统创建的,但是是父进程让操作系统创建的。可以通过getppid来获取父进程