Linux——进程(上)
很楠不爱 2024-10-13 15:37:01 阅读 85
一.冯·诺依曼体系结构
数据在计算机的体系结构中流动进行加工处理,从一台设备到另一台设备,本质是一种拷贝。CPU不直接和外设打交道,CPU只和内存打交道。外设(输入输出设备)的数据,不是直接给CPU,而是要先放入内存中。
二.操作系统
操作系统是一款进行软硬件资源管理的软件。
操作系统对下进行软硬件管理工作,对上层提供良好,稳定,高效的运行环境。
整个计算机的体系结构为层状结构,操作系统在其中起承上启下的作用。
三.进程
进程文件首先存在于磁盘中,当被调度时,会被加载进入内存,随后由操作系统先描述,创建进程PCB,再组织所有的进程成为一个链表,随后由CPU对进程链表进行管理。
进程=PCB➕自己的代码数据用户要使用操作系统,必须使用系统调用的方式。进程 = 内核task_struct结构体 + 程序的代码和数据。PCB:进程控制块,进程属性的集合。
1.进程的tesk_struct内部属性
(1)启动
./xxxx,本质就是让系统创建进程并运行。我们自己写的代码形成的可执行程序、可执行的系统命令都是可执行文件,在linux中运行的大部分指令操作,本质都是运行进程。每一个进程都要有自己的唯一标识符,叫做进程pid。ctrl + C就是在用户层面终止进程,kill -9 pid可以直接杀掉进程。
2.如何查看进程信息
ps:查看当前系统中对应的进程。axj:a表示all全部,xj表示详细信息。grap XXXX:过滤出只有包含XXXX的信息。grep -v grap:删除grep进程的信息。head -x:显示信息的前x行。ps axj | grap XXXX:查看XXXX进程的详细信息。&&:指令级联符号,能够使多个指令同时进行。while :; do 指令; sleep 1; done:每隔一秒钟执行一次该指令while :; do ps ajx | head -1 && ps ajx | grep XXXX | grep -v grep; sleep 1; done:每隔1秒循环打印出XXXX进程的信息。
3.进程创建的代码方式
需要头文件:#include<sys/types.h>,#include<unistd.h>:
pid_t getpid(void):获得当前进程的进程id。pid_t getppid(void):获得当前进程的父进程id。pid_t fork(void):从已存在的进程中创建一个新进程,二者互为父子进程。fork函数的返回值有两个,成功创建子进程,则返回0给子进程,同时返回子进程的pid给父进程;创建失败返回 -1。
创建一个进程,本质是系统中多了一个进程,多了一个内核task_struct,有自己的代码和数据。
父进程的代码和数据从磁盘加载而来,子进程如果没有自己的代码和数据,则会默认继承父进程的代码和数据(子进程会继承父进程全部的代码,但是只会执行fork函数之后的所有代码)。
进程具有独立性,父子进程任意一方被杀掉都不会影响另一方。
bash:命令行解释器,整个系统指令执行的父进程。
chdir(路径)函数:改变进程的执行路径。
进程的信息可以通过 /proc 系统文件夹查看。
4.进程状态
R(running)状态:进程运行的状态。S(sleeping)状态:进程在等待“资源”就绪,可以被中断的状态。T(stopped)状态:让进程暂停,等待被进一步唤醒。t(tracing stop)状态:被追踪而导致的进程暂停,如调试断点。D(disk sleep)状态:不可被杀,深度睡眠状态,不可中断睡眠。Z(zombie)状态:僵尸状态。X(dead)状态:僵尸进程被释放后的状态。
kill -l指令:显示进程控制信号。
(1)僵尸进程
子进程比父进程先退出,还未被父进程回收,需要维持自己的退出信息,在自己的进程task_struct会记录自己的退出信息,未来让父进程来进行读取,此时的子进程为僵尸进程。如果没有父进程,僵尸进程会一直存在。
僵尸进程不能被kill掉。
僵尸进程不被关心的原因:直接在命令行中启动的进程,它的父进程是bash,bash会自动回收新进程的Z。
(2)孤儿进程
父进程比子进程先退出,子进程就会成为孤儿进程。
孤儿进程一般都会被1号进程(OS本身)进行领养的,保证子进程的正常回收。
孤儿进程可以被kill掉。
(3)运行状态
并发:让多个进程在一个CPU内以时间片轮转调度算法等切换的方式进行调度,在一个时间段内得以同时推进代码。
并行:任何时候都同时有多个进程在多个CPU内在真的同时运行。
(4)阻塞状态
阻塞状态对应于进程的S状态和D状态。
所谓阻塞,即等待,等待键盘资源是否就绪,键盘上面有没有被用户按下的按键,按键数据交给进程。
当某个进程需要通过外设获取数据时,其本身会从进程的运行调度队列里断开,而连入外设的等待队列中,此时即为阻塞状态,当获取到数据后,则会重新回到进程运行队列。
阻塞和运行的状态变化,往往伴随着pcb被连入不同的队列中,入队列的不是进程的什么代码和数据,而是进程的task_struct。
不是只有CPU才有运行队列,各种外设也有自己的wait_queue。
(5)挂起状态
当内存中有很多进程处于阻塞态时,大量的代码和数据导致内存吃紧,此时有一部分代码和数据将被唤出进磁盘的swap分区,从而减轻内存压力,当进程被唤醒时再将代码和数据重新唤入内存,我们将进程的代码和数据被唤出到磁盘的这种状态称为挂起状态。
四.寄存器
CPU内部有非常多的各种各样的寄存器,会保存进程的临时数据,CPU内部所有寄存器中的这些临时数据称为进程的上下文。
当一个进程未执行完毕,但是需要临时停止而被切换执行其他进程时,寄存器会临时保存其上下文数据,并记录其当前代码的执行位置,等到该进程被切换回来时,再由寄存器恢复其上下文数据,继续执行该进程。
寄存器本身是硬件,具有数据的存储能力,CPU的寄存器硬件只有一套。
CPU内部的数据,可以有多套,有几个进程,就有几套和该进程对应的上下文数据。
寄存器 != 寄存器的内容
五.进程优先级
指定进程获取某种资源(CPU)的先后顺序称为优先级。
linux中优先级数字越小,优先级越高。
进程访问的资源(CPU)始终都是是有限的,所以要有优先级。系统中进程大部分情况都是有较多的。
操作系统关于调度和优先级的原则:
分时操作系统,基本的公平,如果进程因为长时间不被调度,就造成了饥饿问题。
1.优先级特点及查看优先级
ps -al指令:查看所有启动的进程。PRI:默认进程优先级,为固定数值(80)。NI:进程优先级的修正数据,nice值,新的优先级=优先级+nice,达到对于进程优先级动态修改的过程。nice值并不能被任意调整,而是有范围的。[-20,19]--40个数字。
优先级调整:
top指令:进入top指令后按“r”,回车,在输入进程的pid,回车,最后输入nice值。
每次调整优先级,都是从80开始的。
六.命令行参数和环境变量
1.命令行参数
int main(int argc,char *argv[])
{}
main函数的参数可带可不带。
参数的意义:
argc表示argv[]数组的元素个数。argv[]表示一个字符串数组,以NULL结尾。
main函数的参数表示命令行中,用户输入的若干个指令(以空格为分界的若干个字符串)。
命令行参数本质是交给我们程序的不同的选项,用来定制不同的程序功能。命令中会携带更多的选项。
父进程的数据,默认能被子进程看到并访问。
命令行中启动的程序,都会变成进程,其实都是bash的子进程。
2.环境变量
linux中,存在一些全局的设置,表明,告诉命令行解释器,应该去那些路径下寻找可执行程序。系统中很多配置,在我们登录linux系统的时候,已经被加载到了bash进程中(内存)。bash在执行命令的时候,需要先找到命令,因为未来要加载。PATH:环境变量,是Linux系统下搜索可执行程序的默认路径。$PATH:打印环境变量内容。最开始的环境变量不是在内存中,而是在系统对应的配置文件中。环境变量具有系统级的全局属性,因为会被子进程继承下去。本地变量只在本bash内部有效,无法被子进程继承下去,只有导成环境变量,才能被获取。
bash进程启动的时候,默认会给子进程生成两张表:
用户输入命令行形成的:argv[]命令行参数表;从OS配置文件中得到的:env[]环境变量表。bash会通过各种方式将表交给子进程。
echo $PATH:查看环境变量所处路径。
sudo cp/rm XXXX 路径:让XXXX可执行程序成为(去除)某路径下的系统可执行程序。
PATH=$PATH:路径:将某路径添加成为系统中搜索可执行程序的默认路径。
系统的配置文件有:.bash_profile;.bashrc;/etc/bashrc。
环境变量会在Linux系统重新启动时重新配置,所以只有在系统的配置文件中添加新的环境变量,才会永久存在。
env:查看系统所有环境变量。echo $XXXX:查看单个环境变量。export:创建新的环境变量。unset:删除环境变量。
代码获取环境变量的方法:
头文件:#include<unistd.h>(前)#include<stdlib.h>(后)
extern char **environ:声明全局的环境变量指针,用来获取环境变量表中的环境变量;通过main函数参数;使用getenv("环境变量")函数,获得指定的环境变量。
linux系统中80%的命令由bash创建的子进程执行,称为普通命令,还有一些命令由bash亲自执行,称为内建命令。
七.进程地址空间
进程的代码和数据默认保存在内存中,但是进程不能直接从内存中获取数据,而是需要通过地址空间的虚拟地址,借助页表的映射得到内存的物理地址,进而访问到数据。
地址空间的本质是内核中的一个struct结构体对象,内部很多的属性都是表示start,end的范围。
子进程会把父进程的很多内核数据结构全拷贝一份。
1.写时拷贝
当一份全局数据被父子进程共用时,如果子进程对该数据进行修改,则会另开一个空间对原数据进行拷贝,再进行修改,子进程指向新地址,父进程指向原地址,但两者表面上仍然共用同一个虚拟地址。
2.为什么存在地址空间
通过页表映射将内存中无序的数据变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域。
能够使进程管理空间和内存管理空间结藕。
拦截非法请求,对物理内存进行保护。
3.理解虚拟地址
虚拟地址也叫逻辑地址,是程序中本身就有的地址。
当程序加载到内存时,地址空间从程序获取数据,页表从程序获取虚拟地址,同时内存给出程序的物理地址,进而由页表组织形成映射关系。
八.内核进程调度队列
Linux系统中每一个CPU都有一个运行队列:
从图中我们需要认识两个数组:
long bitmap[5]:32*5的160位的位图,用于判断队列某个位置是否存在进程。
task_struct queue[140]:后40位为进程队列,与进程的优先级40位nice一一对应。
其中,蓝色方框和红色方框为完全相同的进程队列结构,蓝色队列进程只出不进,红色队列进程只进不出,分别通过*active和*expired两个指针管理。
当蓝色队列进程出完或红色队列进程进满时,两者会进行进出功能交换,实际为交换两个指针。
这种进程管理方法称为大O(1)的时间片轮转调度算法。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。