网站建设丿找vx cp5173,更多标签的适用场景,嘉兴装修公司做网站,女生学ui设计难吗深入篇【Linux】学习必备#xff1a;进程理解(从底层探究进程概念/进程创建/进程状态/进程优先级#xff09; 一.进程概念(PCB/task_struct)二.查看进程(top/ps)三.创建进程(fork)四.进程状态(僵尸进程/孤儿进程)五.进程优先级(PRI/NI) 一.进程概念(PCB/task_struct)
1.什么… 深入篇【Linux】学习必备进程理解(从底层探究进程概念/进程创建/进程状态/进程优先级 一.进程概念(PCB/task_struct)二.查看进程(top/ps)三.创建进程(fork)四.进程状态(僵尸进程/孤儿进程)五.进程优先级(PRI/NI) 一.进程概念(PCB/task_struct)
1.什么叫进程呢 一个已经加载到内存中的程序就叫做进程(任务)。 2.对于一个进程操作系统是如何管理的呢 先描述再组织 任何一个程序在加载到内存时形成真正的进程是操作系统要先创建描述进程属性的结构体对象即PCB。 因为我们都是从属性来认识事物的本身的当属性够多这一堆属性的集合就是目标对象。所以描述进程当然是要描述这个进程的各种属性。 3.进程内核数据结构(PCB)自己写的代码和数据。 ①进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合。在OS操作系统下这个进程控制块我们称为PCB。 ②而在Linux下我们称为task_struct。里面包含了进程的所有属性最基本的组织进程task_struct方式是采用双向链表组织。 4.进程属性都有哪些呢 PCB或task_struct结构体的内容是什么呢我们可以将它们分类成下面一些 ①标识符用来描述本进程的唯一标识符可以区别其他进程。 ②状态进程的状态是如何是在任务状态还是在退出状态。 ③优先级进程之间是有优先级的用来竞争CPU。 ④程序计数器程序中即将被执行的下一条指令的地址。 ⑤上下文数据进程执行时处理器的寄存器中的数据。 ⑥内存指针包括程序代码和进程相关数据的指针。 ⑦I/O状态信息包含显式的I/O请求分配给进程的I/O设备和被进程使用的文件列表。 5.进程被描述完后是如何组织起来的 在内核源代码中可以找到它所有运行在系统里的进程都以task_struct链表的形式存着内核中。
二.查看进程(top/ps)
1.进程的信息可以通过/proc系统文件来查看。(进程信息都放在/proc文件中) 2.查看进程还可以用top和ps这些指令获取。 比如查看test进程信息 比如我创建一个程序test运行起来后这个程序加载到内存中就变成进程了。所以我们就可以通过ps指令来查看这个进程的属性。 3.我们还可以通过系统调用来获取进程的信息比如进程的标识符。 进程id(PID) 获取自己进程的pid可以使用系统调用函数getpid(). 父进程id(PPID) 获取自己父进程的ppid可以使用系统调用函数getppid(). 这个进程的pid是27734这个是没有问题的(上面是27733下面是27734其实是一个进程因为我把这个进程中断了然后又运行起来了最后pid不一样了所以我们可以看出来每次运行时pid可能不同但ppid却相同)我们可以查看一下这个pid所对应的进程。不过我更想知道这个进程的父进程是谁呢等会我们可以查看一下。 我们可以清楚的看到27734是对应着./proc这个程序的。而它的父进程是bash。 所以我们可以明白这个进程是由bash创建的我们写的程序都是由bash创建进程运行的。而bash只负责创建进程而子进程坏不坏跟它没有关系它是不管的所有它的ppid不变。
三.创建进程(fork)
1.我们手动输入命令让程序加载到内存变成进程。这个方式是由bash创建进程的。 2.我们还可以通过系统调用fork创建进程即在程序运行时创建进程。而要使用fork()创建进程需要理解以下几点。 fork()函数—创建进程 特点①有两个返回值。当进程创建成功后会将进程pid返回给父进程将0返回给子进程。②fork之后通常要用if进行分流。 1.为什么fork()要返回不同的返回值 返回不同的值是为了让父子进程执行不同的代码块。父进程为了取反不同发子进程通过pid控制子进程。 2.为什么要创建子进程呢 为了让父和子执行不同的事情而实现这样的方式就是返回两个返回值。 3.一个函数是如何做到有两个返回值的呢 fork函数的主要功能就是创建子进程进程创建成功后也就是系统中多了一个PCB。但这个PCB并没有数据和代码。所以系统会让这个子进程共享父进程的代码。理论上数据也要共享的但是进程之间是相互独立的各自互不影响所以要求父子进程不能共享同一份数据。子进程是重新开辟一块空间将父进程的数据拷贝一份的但这样的做法有点低效因为子进程并不一定全部访问父进程的数据这样就会占用内存。所以这里采用的方法是写时拷贝(用多少拷贝多少)。就是当子进程想访问父进程某个具体数据时那就临时拷贝出来。 而fork函数里在返回返回值之前子进程就已经创建好了所以这个子进程是共享fork函数的代码。也就是父进程会返回一次子进程也会返回一次所以最后返回两次。 4.一个变量为什么可以存不同的值 这个涉及程序地址空间。将返回值返回给数据这个过程是在写数据而根据上面的子进程数据是重新开辟一块空间临时拷贝的。 所以这时候将返回值返回到不同的内存空间父进程访问的是老数据空间子进程访问的是一个新的拷贝空间。 5.bash是如何创建子进程的 在理解fork之后我们肯定可以想到bash创建子进程肯定用到了fork函数。一个进程用来打印命令行一个进程用来创建。 1 #include stdio.h2 #include unistd.h3 int main()4 {12 //fork如果创建成功会将当前进程的pid传给父进程将0传给子进程。如果失败 则传返回-113 printf(begin,我是一个进程我的pid是%dppid是%d\n,getpid(),getppid()) ;14 pid_t id fork();15 17 if(id0)//子进程18 {19 while(1)20 {21 22 printf(我是子进程我的pid是%d,ppid是%d\n,getpid(),getppid());23 sleep(1);24 } 25 }26 else if(id0)//父进程27 {28 while(1)29 {30 printf(我是父进程我的pid是%d,ppid是%d\n,getpid(),getppid());31 sleep(1); 32 }33 }34 else//创建失败35 {36 37 }38 return 0;39 } 我们可以发现父进程就是当前正在运行的进程。而当前正在运行的进程的父进程就是bash。
四.进程状态(僵尸进程/孤儿进程)
想要理解一个正在运行的进程是什么意思我们首先要知道进程的不同状态。一个进程可以多个状态。 在操作系统学科中主要有运行状态阻塞状态挂起状态。
1.运行状态 通常来说一个进程在CPU上使用资源叫做运行状态而现实是操作系统中有很多想要运行的进程这些进程用链表组织起来而每个CPU都会维护一个叫做运行队列当进程想要运行时就将自己链入到运行队列中即可。而在运行队列中的进程就可以是运行状态了。 那进程如果没有完成任务会一直在CPU上运行吗当然不会每个进程都有一个叫做时间片。在运行一定时间后就会退出CPU等下次再运行这样就不会存着一个进程一直在CPU上跑了。也说明在一个时间段内所有进程都会被执行一遍这个行为叫做并发执行。 而这一过程必定存着许多行为把进程放上CPU从CPU中放下进程这一过程叫进程切换。
2.阻塞状态 一个进程在等待某种资源时就是在阻塞状态比如当一个进程想要获取从键盘上输入的数据时但键盘就不输入时这个进程就得不到数据就要一直等键盘输入数据这时就是阻塞状态。操作系统在管理硬件资源时也是采取先描述再组织的方法将每个硬件资源都描述成一个结构体对象。而且每个结构体对象里面都有一个等待队列。当一个进程在等待这个硬件资源时就会将这个进程链入对应的等待队列中当有多个进程等待时就将这些进程都链入队列中直到进程进入就绪状态才会将进程直接链入运行队列中。 3.挂起状态 要理解当一个进程处于阻塞状态时它的数据和代码是没有使用的但还是占用着内存资源当操作系统内存资源严重不足时操作系统会将处于阻塞状态的进程对应的代码和数据放入磁盘里只保留着内核在等待队列中等待。这个进程就是挂起状态。当进程进入运行队列时操作系统才考虑将对应的数据从磁盘中再换去到内存中。 各种进程状态的本质就是决定当前进程的PCB在哪个队列里排队
以上都是操作系统学科关于进程状态的一些介绍理解而下面则是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 */
};R运行状态但并不意味着进程一定在运行中它表明进程要么在运行中要么在运行队列里。 S睡眠状态(浅度睡眠)意味着进程在等待着某件事完成。这个进程一定在等待某种资源。 D休眠状态(深度睡眠)这个进程在等待过程中是不可以被调度的不响应任何需求。让进程在等待磁盘写入完毕期间这个进程是无法被杀掉的。在这个状态的进程通常会等待IO的结束。 T停止状态可以通过发送信号让进程停止即这个进程进入T状态这个暂停的进程还可以通过发信号继续运行。 X死亡状态。这个状态只是一个返回状态不会任务列表中看到。 4.僵尸进程 【特点】 当一个进程结束后并不会立刻销毁而是会等待父进程来访问它当父进程给出回应后这个进程才会真正的结束。 进程一般退出的时候如果父进程没有生成回收子进程的信号子进程就会一直让自己处于Z状态即僵尸状态进程的相关资源尤其是task_struct结构体就不能被释放。
所以当进程退出并且父进程没有读取到子进程退出的返回代码就会进入僵尸进程僵尸进程会一终止状态保持在进程表中并会一直等待父进程读取退出状态代码这个状态会一直占用资源会造成内存泄漏 【危害】 当父进程不读取进程的退出代码时进程就要一直维持退出状态而维持退出状态需要数据维持而这个数据就在task_struct结构体对象里里所以当进程退出状态一直维持那么对象就无法被释放那么就会一直占用内存资源当父进程创建很多子进程并且就不读取子进程的退出代码就会造成大量的内存泄漏。
5.孤儿进程
当父进程提前退出子进程想要退出时肯定会进入Z状态那这个子进程该怎么办呢 当父进程比子进程先退出则这个子进程就会变成孤儿进程那这个子进程就无法退出了吗并不是 这个进程会被操作系统’‘领养’即一号进程。最后这个孤儿进程会被操作系统回收。
五.进程优先级(PRI/NI)
输入命令 ps -l 后可以看到 PRI代表着这个进程的优先级其值越小优先级越高越先被执行。 NI代表这个进程nice值nice是用来修正优先级的。 1.nice是如何修正优先级的呢 PRI(new)PRI(old)nice. PRI(old)一直都是80. 2.nice值的取值范围是[-20,19]而进程的优先级范围就是[60,99],所以进程有40个优先级可以调度。 3.可以使用top命令来更改进程的优先级 输入top命令后再输入r会要求你输入要修改进程的pid输入完后会要求你输入要修改的nice值是多少 4.操作系统是如何根据优先级来先后调用进程的呢 我们知道CPU维护着一个叫运行队列的东西运行队列中其实有两个指针数组一个是run数组一个是wait数组run数组里放的是进程地址数组开辟140个空间其中前100个空间是给不同的进程使用我们不用管而后40个空间是用来存放进程地址。 数组的位置和进程的优先级是一一对应的因为进程优先级有40种这里也有40个空间所以100位置上链入着优先级为60的进程地址139位置上链入的是优先级为99的进程。 所以可以根据数组下标的不同从上到下遍历出来的PCB就是根据优先级调度的进程。 而当有新的进程想要运行时并不会再进入run数组里面的队列中而是会进入wait数组的对应优先级的队列中。 运行队列里还有两个二级指针一个是run一个是waitrun就一直指向要运行的数组wait就一直指向新来的进程的数组。 当run中的进程都被调度完后就会将run和wait交换。这样新来的进程又可以被调度了。
而操作系统是如何知道数组中进程是否被调度完呢这个根据位图来实现的 总结 ①在运行队列中有两个指针数组。 ②在这个队列中的进程都是R状态。 ③调度优先级的本质把PCB链入到对应队列的子队列那一个下标中调整优先级的本质就是调整PCB在这个数组中的顺序。 ④所以在调整优先级的同时会改变PCB的存放位置位置不同就表明优先级不同。