ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Linux系统】OS、进程PCB、状态、进程的切换和调度,虚拟地址空间

【Linux系统】OS、进程PCB、状态、进程的切换和调度,虚拟地址空间 文章目录1、操作系统OS2、进程2.1 PCB和进程ID2.2 进程状态和优先级2.3 进程切换和调度2.4 命令行参数2.5 环境变量2.6 进程虚拟地址空间1、操作系统OSCPU在数据层面不会和外设直接打交道只会和内存进行交互任何程序运行都必须先被从磁盘加载到内存当数据在计算机内部流转的时候本质是在不同的设备间进行拷贝所以设备间拷贝的效率就是计算机的效率操作系统包括内核任务管理文件管理内存管理驱动管理其他程序函数库shell程序等OS本质是一种进行软硬件资源管理的软件。为什么要有OS操作系统对下软硬件资源的管理稳定的、高效的、安全的、能进行良好工作的手段操作系统对上要给用户提供一个稳定的、高效的、安全的运行环境目的OS是如何管理的用struct结构体等描述再用链表等数据结构管理。如何理解系统调用和函数库在开发角度操作系统对外会表现为⼀个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。系统调用在使⽤上功能比较基础对用户的要求相对也比较⾼所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于更上层用户或者开发者进行二次开发。2、进程2.1 PCB和进程ID进程是程序的一个执行实例是担当分配系统资源CPU时间内存的实体。进程 内核数据结构 程序的代码和数据。把程序运行起来本质就是在系统中启动了一个进程。进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合。Linux操作系统下的PCB是task_struct。组织进程所有运行在系统里的进程都以task_struct链表的形式存在内核里。查看进程进程的信息可以通过/proc系统文件夹查看。如要获取PID为1的进程信息你需要查看/proc/1这个文件夹。大多数进程信息同样可以使用top和ps这些用户级工具来获取。通过系统调用getpid()和getppid()获取进程ID#includestdio.h#includesys/types.h#includeunistd.hintmain(){printf(pid: %d\n,getpid());printf(ppid: %d\n,getppid());return0;}执行完就退出比如ls、pwd等一直不退直到用户主动退出getpid获得进程的pid2.2 进程状态和优先级新建文件时如果不指定地址会新建到当前目录下这是因为每个进程都会记录下来自己对应的程序是谁还会记录下来自己这个程序启动时所处的工作目录这个工作目录可以更改chdir。proc不是磁盘级的文件fork()-两个进程-父子关系-代码共享但是数据各自一份进程 内核数据结构代码和数据子进程只有内核数据结构代码使用父进程的。进程具有很强的独立性多个进程之间运行时互不影响即便是父子。fork会有两个返回值因为共享了代码。fork之后谁先运行由OS的调度器自主决定。1、并发和并行CPU执行进程代码不是把进程代码执行完毕后才执行下一个进程而是给每一个进程预分配一个时间片基于时间片进程调度轮转。也就是说多个进程在一个CPU下采用进程切换的方式在一段时间内让多个进程都得以推进称之为并发。而站在我们的角度看感觉不到这种切换是因为CPU切换的速度非常快。这也就是为什么当我们的程序有死循环时电脑也不会挂其他的程序还可以运行。多个进程在多个CPU下同时运行叫做并行。2、时间片Linux、Windows等民用级别的操作系统通常都是分时操作系统它的特点是追求调用任务的公平性。3、进程等待的本质连入目标外部设备CPU不再调度。只要进程在运行队列中该进程就处于运行状态随时可以被CPU调度。运行和阻塞的本质是操作系统让不同的进程处在不同队列中。数据结构的增删查改进程卡住实际上是CPU不调度它了也可能是调度周期太长也就是进程太多了。4、当内存资源严重不足的时候操作系统把处于阻塞状态下的进程的代码部分换出到磁盘中称为阻塞挂起状态在阻塞状态转为运行状态前再将磁盘中的代码换入到阻塞队列中换出换入时IO操作这是用时间换空间的做法。有些场景中不适合操作当内存资源严重不足时可能会直接杀掉进程。当有printf时是S休眠状态这是因为一个进程的时间片是非常短的printf并不是直接往显示器上打印而是先打印到内存中的输出缓冲区中速度快时缓存很容易写满而显示器外设并不是时时刻刻就绪有printf就有IOIO的速度很慢死循环过程中大部分的时间都在做IO执行printf时才是R状态由操作系统放到运行队列中被CPU调度这个速度很快。等待键盘和等待磁盘是不一样的处理方案等待键盘S可杀掉进程等待磁盘D不可杀掉进程不常见。僵尸进程子进程结束(调用exit或return退出)释放用户空间、堆栈、代码段但未被父进程读取退出信息(状态)导致进程描述符task struct仍在内核中占用空间。SIGCHLD信号当子进程的状态发生变化时会向父进程发送这个信号目的是让父进程知道这件事让他决定做什么处理。如何避免僵尸进程忽略SIGCHLDsignal(SIGCHLD,SIG IGN)Linux会自动回收(推荐)wait()/waitpid()父进程回收子进程双fork一般T状态也是在等待某个条件就绪。进程被暂停再启动这个程序就到后台去运行了S后面没有这时候我们无法ctrl c终止只能使用kill -9 pid终止。可以执行程序后面用空格来让一个进程到后台运行后面显示进程pid。打断点的本质是让当前进程暂停。进程退出代码不会再执行了可以释放代码和数据但内核数据结构task_struct要被OS维护起来——zombie僵尸状态维持退出信息方便父进程和OS来进程查询父进程或OS需要知道这个进程的执行情况。默认没人管理僵尸状态会一直维持一直占用内存内存泄漏。一般需要父进程/OS读取子进程信息子进程才能退出。孤儿进程父进程退出子进程还在子进程会被系统领养一般会在后台运行。进程优先级确定先后顺序竞争某种稀缺资源。修改重置优先级指令/代码不是高频也建议修改。只能修改nice值。可以通过top命令修改。top r 要修改进程的pid 预期修改的值。系统禁止频繁修改修改nice值的范围为[-20, 20)。为什么nice的修改有范围而不能是任意值呢分时OS注重进程调度的公平。竞争性系统进程数目众多而CPU资源只有少量甚至只有一个所以进程间是有竞争的为了高效地完成任务更合理竞争相关资源便有了优先级独立性多进程运行需要独享各种资源多进程运行期间互不干扰2.3 进程切换和调度进程在运行的时候会有很多临时数据/瞬时数据进程的上下文数据在CPU内部的寄存器中保存。pc当前正在执行指令的下一条指令的地址ir指令寄存器就是正在执行的指令进程切换的核心就是上下文数据的保存和恢复。相关寄存器中内容的保存和恢复CPU中有一套寄存器被多个进程共享调度器要非常均衡地进行进程调度那优先级的出现不是与其冲突了吗1、CPU调度器只会从active队列中选择进程来进行调度2、调度有三种情况运行退出时间片到了有新的进程来了这里有个问题优先级是绝对的吗也就是优先级越高一定最先调度吗如果一个进程优先级在当前运行队列中是最高的那它被调度运行时间片到了后重新插入到运行队列中那它的优先级可能还是最高的那就还是调度它吗如果有新进程来了显然不是因为这样调度是不公平的。当一个进程被调度运行时间片到了后不会继续插入到active队列中而是插入到active的同胞兄弟expired队列中如果有新来的进程也是插入到这个队列中也就是说active队列中的进程只会越来越少当active队列中的进程都被调度一遍后只需要交换active和expired两个指针然后重复这个操作进程调度。也就是说优先级只能保证这个进程在一个调度周期内被优先调度。从上面的图中我们还可以看到有一个大小为5的数组这个数组大小为什么是5呢因为整型有32位5*32160刚好覆盖140个位置。通过类似下面的代码来快速确定队列大致位置一次就可以检索32位for(inti0;i5;i){if(bit_map[i]0)continue;else{//在32个比特位中详细确定哪一个队列}}上面的代码可以保证检索队列在常数范围这种调度算法就是Linux内核O(1)调度算法。所有的进程都要用链表连接进程既可以在调度队列中也可以在阻塞队列中…这是怎么实现的呢意义是什么一个进程既可以在全局链表中又可以在不脱离全局链表的情况下在任何一个数据结构中形成了一张网只要加节点字段即可。原理是什么如果节点结构体中只有前后指针那如何访问数据呢这个问题在C语言中我们就讨论过就是只知道一个结构体中某个成员的地址如果访问这个结构体中其他成员当时使用的方法是计算偏移量。事实上有一个专门计算偏移量的宏offsetof2.4 命令行参数我们知道main函数也是有参数的只是平时不怎么用也不清楚其参数的意义是什么。我们经常使用的指令带选项就是通过命令行参数实现的。通过上面的示例看出命令行参数的意义是什么同一个程序可以根据命令行参数根据选项的不同表示出不同的功能。参数是如何传递的我们启动起来的程序子进程读到了由shell父进程解析的数据。2.5 环境变量环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性。shell的环境变量环境变量相关的命令echo显示某个环境变量值export设置一个新的环境变量env显示所有环境变量unset清除环境变量set显示本地定义的shell变量和环境变量putenv设置特定环境变量的值getenv获取特定环境变量的值前面我们说过命令行执行我们自己的程序时需要带./表示在当前路径下找要执行的这个程序。而执行系统指令比如pwd时不需要指定路径系统默认会到/usr/bin/路径下去找pwd为什么系统知道pwd在usr/bin/路径下呢因为PATH环境变量会告诉shell应该到哪里去找系统指令。PATH中保存的是系统可执行文件的搜索路径集合。所以如果我们不想带./就可以执行我们自己的程序可以有两种方法将我们的可执行文件放到/usr/bin路径下将我们的可执行文件所在路径添加到PATH集合中。但是这种修改只是临时的因为这些环境变量只是加载到bash进程中它是内存级的。这些环境变量开始都是在系统的配置文件中当启动一个shell进程它就会读取用户和系统相关的环境变量的配置文件形成自己的环境变量表这个环境变量表也可以被子进程读取。所以如果我们想自定义一个环境变量并让它永久有效就可以修改源头——系统的环境变量配置文件。当我们登录的时候-系统创建bash进程-读取当前登录用户下的环境变量配置文件-配置它自己的环境变量-将bash自己的路径改为当前用户的路径。进程能获得自己所在的路径通过USER环境变量可以让程序识别用户身份比如可以让某个程序只能指定用户运行环境变量可以被所有bash之后的进程全部看到继承所以环境变量具有全局属性。进程具有独立性但进程间可以通过环境变量进程数据传递一般是只读数据。2.6 进程虚拟地址空间所谓的进程虚拟地址空间本质上是一个内核数据结构对象类似PCB。gval是一个全局变量在子进程中我们修改gval的值在父进程中不修改在父子进程运行的过程中我们读取gval的值可以看到父子进程拥有各自独立的数据但是取gval的地址却是发现一致为什么同一个地址中的gval却有不同的值呢显然这是不可能的事实上我们取出的这个地址只是虚拟地址真实的gval存在不同的物理地址中而虚拟地址和真实的物理地址之间通过页表来建立映射关系实现数据管理。通过上图可以看到同一个变量地址相同其实是虚拟地址相同内容不同其实是被映射到了不同的物理地址。为什么要有虚拟地址虚拟地址空间页表保护内存进程管理 和 内存管理 在系统层面进行解耦让进程以统一的视角看待内存代码和数据可以加载到内存的任意位置通过页表的映射可以让无序变有序本篇文章的分享就到这里了如果您觉得在本文有所收获还请留下您的三连支持哦~
返回列表