Linux操作系统
第2章 进程控制
2.1进程基本概念:
1、进程是程序的一次运行,可以通过ps -aux命令查看系统所有进程。进程实体则是将一个程序运行起来后,它在电脑中的样子,在linux系统中,每个进程拥有独立的虚存空间,每个进程的虚存空间又被分为用户空间和内核空间,运行一个可执行的ELF文件后,文件的代码部分连续存放在该进程用户空间的低地址部分,数据部分连续存放在用户空间略高处,堆栈部分连续存放在用户空间高端(但这3部分在虚存上不连续)。而内核空间则存放该进程的task_sruct、mm_struct、vm_area_struct,结构体task_struct即该进程的PCB,里面存放该进程的一系列信息,task_struct中有指向mm_struct的指针,mm_struct为内存描述符,记录进程在内存中的分布,mm_struct中有指向VMA链表的指针,VMA链表由多个vm_area_struct组成,每个vm_area_struct结构体中存了一段虚存连续空间的起始地址等信息
2、进程间的组织关系:父子(父进程创建子进程)、兄弟(同个父)、会话(一个终端)、进程组(一条命令)
3、pstree命令会以树的形式显示出各进程间的父子兄弟关系
ps -efj命令可以看到进程的父进程(PPID)、所属的会话(TTY)、进程组(PGID)
4、c中,unistd.h中的int fork(void)函数可以创建一个和当前进程执行程序相同的子进程,调用该函数后,会复制出一个完全相同的子进程实体,各执行各的。对于父进程实体中的程序,执行该函数后会返回所创建的子进程的id号,而对于子进程实体中,程序会从该函数处开始执行,并且该函数仅仅返回0(不会再去执行fork)。
5、unistd.h中的execve(const char *path, char *const argv[], char *const envp[])函数则可以使当前进程变为另一个你想执行的进程。第一个参数是你要执行的另一个进程的可执行文件的路径,第二个是该可执行文件需要的参数,第三个是环境变量。执行该函数后,当前进程将完全变身另一个进程(你所指定的可执行文件),所以该函数后面的程序将不会执行,转而去执行另一个进程的程序。(第二三个参数数组要以NULL结尾)
该函数一般配合fork使用,则可以创建出执行某个指定程序的子进程。
6、子进程结束后,大部分资源会被系统回收,但会留下一部分如退出状态信息等给父进程去检查后回收
7、孤儿进程:父进程一般需要在最后调用wait(NULL)函数,去等待子进程执行结束后自己再结束。若父进程没有wait,则当父进程结束后,子进程将变为孤儿进程,由1号进程领养,并做后续的检查回收工作。
僵尸进程:子进程执行结束,父进程还没去检查回收子进程的残余资源的这段时间内,子进程都将处于僵尸进程的状态
8、c中pthread.h中的pthread_create函数可以创建一个线程,让另一个线程执行指定的函数。线程共用同一套进程实体,只不过是在同时执行不同的函数,不同的执行流而已,所以创建线程仅增加了task_struct(PCB)和vm_area_struct,分别用于记录线程运行的信息以及属于线程自己的堆栈空间的位置信息。而线程不增加额外的mm_struct,因为其共享进程自身的mm_struct。(注:编译时要加-lpthread)
9、LWP为线程号,NLWP为线程所属进程的线程总数,进程5497有两个线程,分别是5497和5498
10、在/proc/slabinfo中记录了当前系统中task_struct、mm_struct、和vm_area_struct的信息,可以通过cat /proc/slabinfo|grep xxx查看
第3章 进程调度
1、一个系统主板上可以插多个物理CPU,而每个物理CPU可以有多个物理核,每个物理核就是一个逻辑CPU,如果是支持硬件线程技术的CPU,每个物理核还能对应多个逻辑CPU。Linux操作系统只关注逻辑CPU,每个逻辑CPU上可以运行一个进程,拥有独立的调度队列。
2、用cat /proc/cpuinfo指令可以查看CPU的具体信息。processor对应不同的逻辑CPU的编号,physical id为该逻辑CPU所属的物理CPU,core id为该逻辑CPU所属的物理核心,siblings即兄弟的意思,表示同个物理CPU中有多少个逻辑CPU。若两个逻辑CPU的physical id和core id都相同,则表示该物理CPU支持硬件线程技术,这两个逻辑CPU都是位于同一个物理核上的。
3、每个逻辑CPU有独立的运行队列rq(run queue),每个rq中有4种“调度类”,分别是stop、rt、cfs、idle,紧急程度从左往右递减,stop中放的是最紧急的进程(如断电),会被最优先调用,rt为实时进程,cfs为完全公平调度类,里面存放的是普通的进程,idle为空转。stop中的进程执行完后,才会调度rt类的进程,rt和stop中的进程都执行完后,才会调度cfs类的进程,以此类推。除此之外,系统还会进行负载均衡,如图中CPU0有多个rt进程要执行,而CPU2、CPU3只是在执行紧急程度比较低的cfs和idle进程,则系统会将CPU0中的rt进程迁移到CPU2、CPU3中去执行。
(注:rq中的进程都是处于就绪状态的,可以随时接受调度的,rq相当于就绪队列)
4、通过cat /proc/sched_debug可以查看到每个逻辑CPU下进程的情况。这里以CPU0为例:
===============================================================================
nr_running为CPU0中的进程总数(包括正在运行的)
curr->pid为该逻辑CPU正在执行的进程id
next_balance为下次进行负载均衡的时间
===============================================================================
CPU0中cfs调度类中的进程的情况,nr_running为cfs类中的进程数(包括正在运行的)
===============================================================================
CPU0中rt调度类中的进程情况,rt_nr_running为rt类中的进程数(包括正在运行的)
===============================================================================
…
所有在CPU0上运行的进程
===============================================================================
5、通过top指令S那栏可以看到各进程的状态,R为就绪或正在运行的进程;S为等待某资源的阻塞状态的进程;T为暂停状态,当键入ctrl-z后,前台进程组暂停执行放进后台;t为被跟踪状态,当debug程序停在断点处时,该进程处于被跟踪状态;Z为僵尸进程。
6、Linux中对CFS调度类中的进程(即普通进程)的调度采用的是CFS调度算法。
系统为每个进程维护一个虚拟执行时间,虚拟的表示该进程执行了多久。每次调度了某个进程并执行后,会更新该进程的虚拟执行时间。不同权重load的进程,即使执行了相同的实际时间,其虚拟时间的增加也是不同的。权重越大的进程,每次执行相同的实际时间,所增加的虚拟执行时间会更小,反之亦然。而每次系统调度时,会选择虚拟执行时间最少的那个进程进行调度,这就使得权重大的进程,其虚拟执行时间增加缓慢,总是会比较小,所以能更多的被调用,其实际的执行时间更长。
进程的权重由进程的优先级决定,优先级越小,进程的权重就越大,获得的cpu资源越多。而进程的优先级由进程的nice值决定(优先级=nice值+20),nice越小,优先级越小,越优先(nice越小,表示这个进程一点都不nice,想抢占很多资源)
(nice取值范围为-20到19,默认为0,所以进程优先级默认是20)
7、通过top指令可以看到,执行了一个nice为0,优先级为20和一个nice为10,优先级为30的进程。可以发现优先级低的,不那么nice的那个进程,会占用更多的cpu时间(83.4%)。其实际在cpu上执行的时间更多。
8、通过cat /proc/PID/sched指令可以看到这两个进程的许多调度细节,这里截选了一部分。
可以看到,这两个进程的开始执行时间se.exec_start都是差不多的,它们的虚拟执行时间se.vruntime也是相近的(因为系统总会优先调用虚拟执行时间最少的那个进程,使得每个而进程得到的虚拟的执行时间是尽可能相同的,来确保公平)。而其实际执行时间se.sum_exec_runtime,则差别很大,权重大的进程,其实际执行时间更多。
9、nice -10 ./xxx & 执行可执行文件xxx,并设置该进程的nice为10(&为后台执行)
sudo nice --10 ./xxx 执行可执行文件xxx,并设置该进程的nice为-10(要sudo)
10、Linux中的rt类进程(实时进程)本质上和cfs类进程没区别,只不过 Linux会先将rt中的进程执行完才会执行fcs中的。
rt类进程又分为两类,FIFO和RR。同优先级的RR进程会轮流执行,而同优先级的FIFO进程则按FIFO原则执行(先来后到),而对于同优先级的RR和FIFO进程, CPU将会被FIFO进程独占(因为RR比较友好,对同优先级的会让,想轮流执行,而FIFO进程一旦拿到CPU,除了被更高优先级的打断,它是不会让的)
11、可以用c中sched.h中的sched_setscheduler(int pid,int type,struct sched_param)函数将普通进程设置为rt类,第一个参数为所要设置的进程id,第二个参数填1为rt_fifo;2为rt_rr,第三个参数接收一个sched_param的结构体,该结构体中可以设置进程的优先级。
struct sched_param my_params;
my_params.sched_priority=90;
sched_setscheduler(0,1,&my_params); //将本进程设置为fifo类进程,并设置其优先级为90
(注:rt类进程priority取值范围为1-99,且值越大越优先)
12、cfs进程的负载均衡:每个cfs进程都有负载值load(前面说了它和优先级直接相关),系统周期性的检查各逻辑CPU的负载进行负载的均衡(可以通过/proc/PID/sched中的se.load.weight和se.nr_migrations查看进程的权重以及被迁移的次数)(可以通过top-f-移动光标到P-空格选中,增加cpu列表,实时查看各进程所在的cpu)
rt进程的负载均衡:可参见3对rt类进程的迁移机制