文章目录
- 📖 前言
- 一、冯诺依曼体系结构
- 二、操作系统
- 2.1 概念
- 2.2 核心功能
- 2.3 系统调用
- 三、进程
- 3.1 基本概念
- 3.2 task_struct
- 3.3 查看进程
- 3.4 代码创建子进程
- 三、进程状态
- 3.1 名词提炼
- 3.2 运行状态
- 3.3 阻塞状态
- 3.4 挂起状态
- 3.5 内核链表
- 3.6 具体状态
- 3.6.1 运行状态(R )
- 3.6.2 休眠状态(S)
- 3.6.3 追踪状态(t/T)
- 3.6.4 深度睡眠状态(D)
- 3.6.5 结束状态(X)
- 3.6.6 僵尸状态(Z)
- 3.6.7 挂起状态
- 3.6.8 孤儿进程
- 3.6.9 补充
- 文章结语
📖 前言
操作系统学习有三座大山:第一座是进程概念,第二座是文件系统,第三座是线程。今天,我们先来学习第一座大山——进程概念。
一、冯诺依曼体系结构
在生活中,我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
输入设备:键盘,鼠标,话筒,摄像头…,网卡,磁盘
输出设备:显示器,磁盘,网卡,打印机…
CPU=运算器(逻辑运算和算术运算)+控制器(帮我们进行控制CPU去取指令、分析指令)
存储器:内存
磁盘:外存
输入设备和输出设备统称为外设,内存和CPU为亲兄弟
程序运行之前,为什么必须先加载到内存?
体系结构规定!因为CPU获取,写入,只能从内存中来进行!CPU执行我们的代码,访问我们的数据(数据的流动方向永远都是设备把数据移动到内存,内存把数据搬到CPU,CPU把数据转完再搬到内存,然后再用内存的数据搬到外设 没,它其实就是来回搬。)
数据是从一个设备“拷贝”到另一个设备(体系结构的效率:由设备的“拷贝”效率决定!)
为什么计算机是这种结构呢?
在我们整个计算机世界里,存储这件事儿如果离CPU越近,它的存储容量往往越小。但是它更快,但是它更贵,但离CPU越远,它的存储容量越来越大。
比如磁盘。如果绕过内存,由存储分级结构确定,会导致效率低,为了在这个效率和价格之间找平衡,所以要引入内存(用内存来进行对CPU和外设之间速度不匹配做一定的适配),与木桶原理类似,后面有了操作系统会把内存的效果发挥到最好
计算机结构它的效率由谁决定?
是由拷贝效率决定的。既然是由拷贝效率决定的,不同的设备它对应的拷贝效率越靠近CPU,效率越高,所以CPU此时拷数据就不用再和外设拷了,而是从内存拷。
对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程
同学们既然我们两个在进行聊天用的是电脑,本质是两台冯诺依曼体系在进行聊天
二、操作系统
2.1 概念
一个基本的程序集合,称为操作系统(OS),操作系统是一款进行软硬件管理的软件
狭隘的操作系统只包括内核(进程管理,内存管理,文件管理,驱动管理)
广义操作系统还有函数库,shell程序等
设计操作系统的意义是啥
操作系统它存在的意义是第一个它要把向下进行软硬件资源的管理。操作系统必须保证每种硬件,如果它硬件本身是健康的,对下,与硬件交互,管理所有的软硬件资源。对上,为用户程序(应用程序)提供一个良好的执行环境
总结
1.软硬件体系结构层状结构(高内聚低耦合)
2. 访问操作系统,必须使用系统调用-- 其实就是函数 ,只不过是系统提供的
操作系统本身不允许未来的用户直接访问内存,它必须要求用户通过操作系统自己提供的一些C语言封装的接口来访问操作系统,这个接口称为系统调用。我们的软件层中出现了 一个叫做system call。
3. 我们的程序,只要你判断出它访问了硬件,那么它必须贯穿整个软硬件体系结构!
4. 库可能在底层封装了系统调用
2.2 核心功能
操作系统在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
如何理解管理
管理的例子-学生,辅导员,校长(可以类比)
计算机管理硬件,第一步先描述起来,用struct结构体,第二步组织起来,用链表或其他高效的数据结构(比如日常的校长管理学生的工作->对链表的增删查改!),先描述,再组织!对任何管理场景进行建模!
操作系统可以在自己操作系统内部先描述再组织,把网卡、硬盘、显卡、键盘、显示器等统一可以使用一项叫做struct device定义一个类,其中我们的属性里包括硬件的名称,硬件的状态,里面可以包含每一种硬件的各种属性或硬件相关的链接信息,把所有的硬件在内操作系统,因为操作系统是C语言写的,在操作系统内构建struct的device这样的结构,然后每一个设备都要对应一个device对象,把对象管理起来,所以操作系统管理硬件转化成了对硬件的增删查改。同理对进程也是先描述在整理(这个世界也是先描述再组织)
操作系统是进行软硬件资源管理的软件,第一,操作系统是软件,它是由我们的软件工程师编写的。第二,操作系统是进行管理的软件,那它要管什么呢?硬件主要是包括我们基于冯诺伊曼组织的各种硬件结构,软件包括我们的进程,包括我们未来的文件。
操作系统的存在本质上是对下进行软硬件资源管理,又为了给对上提供一个良好的稳定的运行环境,这就是操作系统存在的意义。
2.3 系统调用
操作系统既要向上提供对应的服务,又要不相信任何用户或者人(在生活当中与银行类似)
这个库函数底层有没有调系统调用,就看一个调这个库函数时有没有最终访问硬件
三、进程
3.1 基本概念
表面理解:
课本概念:程序的一个执行实例,正在执行的程序等。
内核观点: 担当分配系统资源(CPU时间,内存)的实体。
3.2 task_struct
在Linux 中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构类型,它会被装载到RAM(内存)里并且包含着进
程的信息。
内容分类
进一步理解:进程=内核数据结构对象+自己的代码和数据!(进程 = PCB(task_struct) + 自己的代码和数据!),这个就跟找工作一样,简历发挥的效果一样
有个结构体来管理进程,进程的所有属性,都可以直接或者间接通过task_struct 找到
程序加载进来的时候,除了把你的代码和数据加到内存里,操作系统还要在自己的内部为该代码和数据创建对应的task_struct结构体,然后这个结构体可以找到代码和数据,并且所有的task_struct在操作系统内可以进行使用,比如说我们叫做链表形式,把所有的PCB管理起来。在操作系统内部,它要管理所有的进程,最后全部都会被转化成对进程链表的增删查改。所以为什么一个进程加载的时候,操作系统要给它创建对应的PCB即task_struct,因为操作系统要管理进程,因为要管理,所以必须得先描述再组织。
组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct双链表的形式存在内核里。
3.3 查看进程
我们历史上执行所有的指令,工具,自己的程序,运行起来,全部都是进程!
getpid:查看当前进程的id
可以先创建一个进程
makefile
myprocess:myprocess.c gcc -o $@ $^ .PHONY:clean clean: rm -rf myprocessmyprocess.c
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { while(1) { sleep(1); printf("我是一个进程!!!,我的pid为 %d\n",getpid()); } return 0; }ps axj:可以查看系统当中所有的进程
top:也可以查看所以进程
当然也可以用ps axj | head -1;ps axj |grep myprocess自己查进程
也可以屏蔽掉ps axj | head -1;ps axj |grep myprocess|grep -v grep
每一次启动同同一个程序,PID的值不一样是很正常的。Linux系统里分配PID其实是通过一个线性递增的整型值不断,在Linux系统里,我们用户是以进程的方式来访问操作系统的。
如果想杀掉进程,除了ctrl+c还可以kill指令杀掉kill -9 4163279
想查看进程还有另一种方法,要查看进程,除了ps命令,我们也可以通过给你呈现出来,让你动态看到内存,在一个Linux当中的目录结构,叫做proc目录。proc就是process的简写系统当中查到的这些都是数据。这也比较符合Linux上一切皆文件的观点。
proc目录存放的是当前系统里所有进程的信息,而该目录下的所有的文件,每一个数字目录代表的就是特定进程的PID,而一旦进程退出, 该目录会被系统自动移除。
文件夹的名字其实就是特定一个进程的PID。一个数字目录里面的内容将来包含的是这个进程在运行时的动态属性,这个目录里面包含的就是当前进程的所有属性
众多属性只需要重点看两个
如果要是把对应的这个文件给它删掉呢,我如果把我这个进程所对应的文件删掉。这个进程还在跑,因为你自己删掉的是磁盘上的这个文件,而进程启动时这个程序的拷贝已经在内存了,所以你把它删掉并没有直接影响这个进程。(但是再查会标红)
cmd是当前路径,程序会记录下来自己的当前路径(也解释了为啥fopen能找到当前路径,因为有pcb),因为你的进程先启动的,先记录下来路径了,你写个文件名时,它就拿着这个路径和你的文件名做拼接
如果你要更改一个进程所处的当前路径,那我们可以使用chdir
Linux系统是一个单亲繁殖的系统,所有的进程都是被它的父进程创建的。一个父进程呢当然可能创建多个子进程,所以Linux当中所有的进程也是进程树,getppid可以查看父进程的id
进一步查父进程
bash其实是命令行解释器(王婆):本质是一个进程!操作系统会给每一个登陆用户,分配一个bash!。操作系统会给每一个登陆用户,分配一个bash!,前面的-代表远程登陆
3.4 代码创建子进程
fork可以创建子进程
myprocess.c
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { printf("父进程开始运行,pid: %d\n",getpid()); fork(); printf("进程开始运行,pid: %d\n",getpid()); return 0; }子进程要创建,一般是把父进程的pcb给子进程拷贝一份, 但是也不完全一样(比如pid),大部分属性是一样的,子进程默认也会指向父进程的数据和父进程的代码,子进程默认也会指向父进程的数据和父进程的代码。
子进程没有自己的代码和数据,因为目前,没有程序新加载!
fork函数的用法
return 返回值
成功了,会把子进程的pid返回给父进程,0会返回给子进程,失败了,-1返回给父进程,子进程不创建
为什么fork给父子返回各自的不同返回值?
因为父进程比上子进程为1:n,父进程可能因为有多个孩子,所以必须拿到每一个孩子的PID,未来方便对子进程做管理
为什么一个函数会返回两次?
Fork这个函数它本质上是一个系统调用。所以Fork它在调这个函数的时候,它一旦调Fork就会进入到Fork函数所对应的系统调用,return他自己也是一条语句,代码共享
为什么一个变量,即==0,又大于0?导致ifelse同时成立?
进程具有独立性!把父子任何一方,进行修改数据,OS把被修改的数据在底层拷贝一份,让目标进程修改这个拷贝!(写时拷贝!),下面可以验证
myprocess.c
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { int count=100; printf("父进程开始运行 \n"); pid_t id = fork(); if(id<0) { perror("fork"); return 0; } else if(id==0) { while(1) { sleep(1); printf("我是子进程 ,pid为%d,ppid为%d,count为%d\n",getpid(),getppid(),count); count+=10; } } else { while(1) { sleep(1); printf("我是父进程 ,pid为%d,ppid为%d,count为%d\n",getpid(),getppid(),count); } } return 0; },
我们父子进程保证独立性,一叫做数据结构独立,因为进程等于内核数据结构。二是代码共享,但代码是只读的,不影响数据。要修改时以写时拷贝的方式各自私有一份,所以当我们的return本质是对该变量进行修改,所以其实这个变量看起来是一个变量,但其实对应两个内容。
三、进程状态
进程状态本质就是task_struct结构体内的一个整型变量,在操作系统内我们可以定义#define,定义宏来表示running,sleep,stop等各种各样的状态,进程什么状态就把我们的整型状态值改成什么数字就可以了
3.1 名词提炼
- R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
- CPU的调度结构
- 由于cpu只有少量的,一个进程他要能够在cpu上去运行,实际上cpu在系统内部维护一个叫做调度队列的东西。
3.2 运行状态
一个进程它持有CPU在进行CPU上跑的时候,它就是运行状态。只要一个进程,在调度队列当中,我们就称该进程叫运行状态(只要在这个队列里,每一个进程的状态都是running。)只要在这个队列里,每一个进程的状态都是running,要么就是正在被CPU运行,要么就是我已经完全准备好了,随时等待被调度,上面所说的这个运行状态,它其实对应的是上面的运行和就绪
3.3 阻塞状态
比如scanf,它的意义是等待某种叫做设备或者资源就绪,比如说你今天要访问键盘scanf,键盘不一定是直接就被撼下了,它可能没撼下,所以你的进程就必须阻塞,如果要往显示器上打印, 可是显示器正在为其他进程占有,显示器消息量太大了,显示器来不及显示了
操作系统要对硬件做管理转化成了对链表的资源查看,理解思路和我们进程一模一样。因为操作系统管理它要先描述再组织,而其中struct device里包含的就是目标设备的所有的属性,它可以直接或间接找到我们对应的数据(OS要管理系统中的各种硬件资源,先描述,在组织!)
如何理解阻塞等待
在运行的时候,执行这个进程的代码发现你这个进程,要进行scanf读取了,在读取时操作系统发现你要读键盘,帮我们去检查键盘的standus状态,发现键盘并没有任何按键被按下standus,当前设备是没有活跃的。操作系统把这个进程从CPU上拿下来,并且把这个进程从运行行队列当中移走,把它pcb链入到我们对应的特定设备的等待队列当中,一旦列入到把这个进程链到其他的队列里,它就不再运行队列里了。进程就永远不会被调度了,这个进程此时就处于阻塞状态。
当磁盘上就绪了(键盘被按下),操作系统必须第一时间知道直接转而查看对应的这个就绪设备的对应的节点,将状态那么设置为比如说active,比如活跃的并且检查等待队列,发现等待队列里指针不为空,那么就将该等待队列的进程重新链接到运行队列,后来cpu调度之下调度这个进程时,这个进程它就会继续运行。曾经的scanf,把数据从外从我们对应的底层,从设备上读到自己的进程上下文当中被进而被我们拿到。
进程状态的变化,表现之一,就是要在不同的队列中进行流动。本质都是数据结构的增删查改!
3.4 挂起状态
挂起是操作系统里面一个比较极端的情况
当等待队列的进程在设备没有就绪的时候,这些进程绝对不会被调动,除非你这个设备就绪了,这个进程才会重新被放到运行队里重新调动。操作系统这个时候在内存资源严重不足的时候,有些数据其实当前并不会被立即访问,比如等待队列的进程的代码和数据,它其实根本就不会被执行。
当内存资源严重不足时,把等待队列换到我们对应的磁盘上,磁盘的有一个分区上。(操作系统就把对应的这批正在阻塞队列当中的进程的代码数据交换到磁盘当中。此时这些进程的状态我们叫做阻塞挂起。一些就是内存块交换到我们对应的磁盘上。当前把代码数据拿出去的没有代码数据,只有pcb的这样的进程,我们称为阻塞挂起),如果键盘就绪了,操作系统先知道,操作系统就会把我们对应进程曾经换入到磁盘,你这个进程所对应的代码和数据重新加载内存,重新构建指针映射让你找到。再把曾经交换到磁盘当中的代码数据换入内存之后,然后形成完整进程。
然后再把这个进程放到运行队列里。这个过程我们称为swap交换分区所对应的换入和换出操作,一个进程,当它所对应的代码数据被放在交换分区,我们称之为进程阻塞挂起。当它换入时,我们将阻塞挂起进程重新改成运行状态,该进程就可以直接继续被调度了。挂起是把该进程的代码和数据挂到外设上,挂到磁盘上。
如果操作系统内部内存资源相当吃紧,把阻塞挂起,阻塞的进程它的代码数据全部挂起到外设上还是不够,那么怎么办呢?
操作系统就只能打我们运行队列当中进程的主意了,这种叫做运行挂起
无论什么挂起,本质是把进程换入换出到我们的磁盘的swap交换到分区
3.5 内核链表
struct list_head里面有个list_head,就可以双链表链接起来
这样只能拿到list的地址,但是要访问任何这个结构群内的任何一个成员,该怎么做呢?
结构体本身的地址和第一个成员的地址是在数字上是相等的,结构体成员的地址是依次增大的,但它的地址排布是从上往下依次是进行增大的,可以拿到偏移量&(((struct task_struct*)0)->links),再拿着list埋值直接减去偏移量,就可以等到头部的地址,再强转成task_struct*,当然,可以在struct_task内部放多个list_head对象,可以用一个struct_list对象把所有进程用双链表连接起来,可以用下一组类似的把这两个进程构成一个队列,所以运行队列又属于全局链表,不是单一的
3.6 具体状态
3.6.1 运行状态(R )
运行状态是放在task_state_array数组中维护,状态就是个整数,这个整数就是这个将来这个整数就是这个数组的下标。未来想打印的时候就可以直接把上面的这个整数值转化成字符串
下面进行验证,让系统检查进程状态
while :; do ps axj |head -1;ps axj |grep myprocess;sleep 1 ;done为啥一直在运行却是Sleep状态,因为你这个进程它执行printf代码,可能说一纳秒,而你的进程其实在一段时间内,比如一秒以内,他可能有一纳秒是在执行代码,剩下的那么一秒减一纳秒,剩余的时间其实你的进程你可以理解成全部都是在监测我们对应的进程,它在等待L,所以我们今天查到它的状态,其实全都是S状态,它其实是在计算机内不断的在运行队列和我们的阻塞队列来回在进行切换
可以让程序死循环,就可以查看的到
其中后面跟着加号,是因为有加号的意思是我们当前启动这个进程时是在前台启动的(就是在命令行上启动的,那而且它会阻塞我们命令行)
3.6.2 休眠状态(S)
比如 当运行scanf时,当前进程pcb就会被列入键盘对应的设备所对应的等待队列里,当我按下的时候会把我重新唤醒,放入运行队列继续运行
3.6.3 追踪状态(t/T)
用cgdb调试的时候可以用到,让这个进程遇到断点就停下来(打断点,被debug,断点:进程被暂停了!)
也可以通过键盘操作Ctrl+z我们此时让这个进程暂停了
小t是有某种用途,比如dbug调试,大T是用户自己暂停的,暂停它其实和阻塞不太一样,也就是和S状态不太一样,S是一个进程在等待某种资源,而这个进程暂停,往往是因为某种条件,就是某种条件不具备,一般我们的这个暂停状态呢,它是用来做止损的,那操作系统呢叉觉得这个进程呢暂停了,暂停了之后,但叉没有把你杀掉的,你的问题又没有那么严重,我只是把它暂停了,剩下的交给用户来决定
3.6.4 深度睡眠状态(D)
一个场景:但进程要求磁盘写入数据(可能很大),存储到你磁盘的特定的位置里面,但是磁盘写入的时候也有可能写入失败,无论成功还是失败,一旦我写入完成之后,我要把结果返回给进程,它必须得把自己设为阻塞状态,所以进程把自己设为S状态,如果当我们对应的系统资源严重不足时,会把进程做各种挂起,还是都无法满足我们对应的让操作系统获得足够多的内存,操作系统有可能在极端情况下采取杀进程的做法,节省空间。为了防止误杀进程,如果是D状态,操作系统你无权杀他,你只能杀掉是S状态的进程。
**在我们的操作系统内,凡是涉及到对磁盘这样的关键数据存储,设备进行访问时,进程进行高IO的时候,进程的状态不设为S而设为D,主要是为了防止该进程出现丢失,进而导致数据丢失的问题。可以归根结底D状态也是阻塞的一种。
阻塞状态包括S和D状态
如果想让进程暂停还有另外一种做法,可以用kill指令,选项为19,也可以kill -l
查看对应的选项+
kill -19 对应线程的pid
18也可以继续执行
kill -183.6.5 结束状态(X)
进程创建创建PCP开辟代码和数据,如果要是死了的话,X状态就是死了,释放我们的pcb、释放代码和数据
3.6.6 僵尸状态(Z)
僵尸状态是为了获取退出信息。我们创建子进程的目的,是为了让子进程完成某种事情的!结果相关的信息,父进程得知道。所以一个子进程退出可以释放代码和数据,它的pcb信息不能释放,只是这个进程不会被调度了,他必须把自己退出的信息暂时维持住
比如写程序时会有main函数,会有返回值,也就是退出码,它会把自己的那个main函数的返回值保存起来,让父进程知道我这个程序退出时它的结果是怎么样,退出信息信息存在task_struct里。
在父进程没有获取它的信息之前,这个进程一直都会处于叫做僵尸状态。
如何模拟僵尸状态
可以用while :; do ps axj |head -1;ps axj |grep myprocess;sleep 1 ;done来查看进程状态
myprocess.c
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main() { int x =0; int count =5; pid_t a=fork(); if(a==0) { while(count) { printf("我是子进程 ,count为%d\n",count); sleep(1); count--; } } else { while(1) { printf("我是父进程 ...\n"); sleep(1); } } return 0; }
如果父进程一直不管,不回收,不获取子进程的提出信息,那么pcb会一直存在!父进程除了去获取子进程的退出信息,它还有一件事情就是要把pcb释放,X状态看不到,因为是X操作系统瞬间就把这个子进程全部释放了
会有内存泄漏问题吗?
僵尸进程是系统级的内存泄漏,像new、malloc一般的不会有问题
什么样的进程具有内存泄漏问题,是比较麻烦的?
常驻内存的进程,比如操作系统自己一旦启动之后,它本身就是个常驻内存的软件,所以它内部一旦有内存泄露问题,那么就影响比较大,并不排除你操作系统内部有内存泄露,因为我们刚刚讲的僵尸问题(用户就要写代码解决这个问题)
3.6.7 挂起状态
挂起状态在我们用户层上挂起状态的本质其实是内核系统内存资源不足,操作系统为了节省内存,来把一个进程的代码和数据交换到交换分区上。完了之后需要调度,需要运行,再把你换入进来。整个操作系统进行换入换出了内存资源,释放了这些工作。作为用户我们需要知道,需要关心,需要了解吗?
答案是我们不需要关心操作系统,这种工作是完全隐藏起来的,挂起状态并不需要在我们的Linux当中体现出来
3.6.8 孤儿进程
可以模拟实现
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main() { int x =0; int count =5; pid_t a=fork(); if(a==0) { while(1) { printf("我是子进程,pid为%d,ppid为%d\n",getpid(),getppid()); sleep(1); } } else { int cnt=5; while(cnt) { printf("我是父进程,pid为%d,ppid为%d\n",getpid(),getppid()); cnt--; sleep(1); } } return 0; }可以用while :; do ps axj |head -1;ps axj |grep myprocess;sleep 1 ;done来查看进程状态,父子进程关系中,如果父进程先退出,子进程要被1号进程领养,这个被领养的进程(子进程),叫做孤儿进程,1号就是操作系统(systemd)
为什么要领养?
如果不领养?子进程进入僵尸之后就没有人管理,能管理的只有父进程和操作系统.
孤儿进程,那么一般它就会直接被系统领养,这个孤儿进程一般自动就会变成后台进程了。
3.6.9 补充
关于内核结构申请的
正常情况下,我们一旦把它这个僵尸状态一回收,那未来下一次再创建进程,你就再继续malloc这个task_struct结构体,可以在系统里可不可以维护一张废弃的taskstrüc对象的列表,下次再要就往这个表里面选择一个task_struct的字段全部初始化一下,全部填成新进程的(类似于数据结构对象的缓存),它就可以加速我们创建进程和释放进程的速度。
当访问某些文件时,本质就是进程在访问文件。我们一个进程,将来拿着自己的uid和文件所对应的uid做对比。
Linux系统中,访问任何资源,都是进程访问,进程就代表用户(用户和你的系统打交道,只能通过进程来和系统交互)
PRI:进程的优先级,默认:80,NI:进程优先级的修正数据,nice值
文章结语
感谢你读到这里~我是「键盘敲碎了雾霭」,愿这篇文字帮你敲开了技术里的小迷雾 💻
如果内容对你有一点点帮助,不妨给个暖心三连吧👇
👍点赞| ❤️收藏| ⭐关注
(听说三连的小伙伴,代码一次编译过,bug绕着走~)
你的支持,就是我继续敲碎技术雾霭的最大动力 🚀
🐶 小彩蛋:
/^ ^\ / 0 0 \ V\ Y /V / - \ / | V__) ||摸一摸毛茸茸的小狗,赶走所有疲惫和bug~我们下篇见 ✨