Linux:进程

先了解一下这篇的基础知识

操作系统简述-CSDN博客还有这篇

ok我们来说进程

进程是什么?

在Windows下我们按下Esc+Ctrl+Shift召唤任务管理器,查看Windows下的进程

我们的进程也是由操作系统管理的,操作系统对进程的管理也是先描述再组织。

进程

课本概念:程序的一个执行实例,正在执行的程序等

内核观点:担当分配系统资源(CPU时间、内存)的实体

每一个进程创建时都会有一个对应的struct PCB。它们本身都存在磁盘里,当你需要时,它们会从磁盘加载到内存里。

什么叫PCB?

PCB(process control block)进程控制块,简单来说是一个结构体,但是是操作系统来管理进程的一个结构体。

每次我们使用电脑开机的过程,都是先加载操作系统。操作系统没开机的时候,也是存在磁盘里的一个二进制文件,是开机时打开的第一个软件,将存在磁盘里的操作系统加载到内存部。进程不仅要将可执行程序的代码和数据加载到内存中,还要给它的结构体PCB也申请一个内存出来,方便操作系统对进程进行管理。

这个PCB怎么用呢?

一个PCB代表了一个进程的储存信息,那么用多个PCB将它们用指针串起来,是不是就形成了一个进程管理的链表?我们对进程的管理也就变成了对链表的增删改查。不用直接加载我们的可执行程序,因为一个一个加载很麻烦(我们有时候也不需要那么多的信息)而是管理我们的进程控制块。

为什么要有PCB呢?

还记得我们刚才说的先描述后组织吗?PCB就在这里起到了一个描述的作用,一个进程=内核task_struct结构体+程序的代码+数据,方便我们的操作系统管理进程。

struct task_struct
{//Linux进程控制块}

操作系统对进程进行运行调度,本质上就是让进程控制块task_struct进行排队,因为他们是描述进程的结构体,你不能让进程一个一个排队,但是可以让描述他们的结构体来排队。

怎样理解进程是动态运行的?

我们的进程可能会出现在各种地方,比如说现在在磁盘等待被加载,然后又在CPU里被运行,然后又在等待显示器、键盘的网络资源。进程动态运行的特点主要体现在CPU或其他设备想要被进程访问执行时,都是以PCB为代表被来回调度运行的。

这个调度运行就是我们进程的动态运行,我们的PCB怎样运行,也就意味着我们的进程怎样运行。task_struct在不同的队列中,PCB节点就被放到不同的地方,进程就可以访问不同的资源。

task_struct属性

启动

./xxxx运行某个程序本质就是让系统创建进程并运行

我们学过的命令在Linux下也是可执行程序

我们自己写的程序本质上也是可执行程序。

查看进程

ps axj

我们在Linux下运行的大部分操作本质上都是运行进程。当然,进程只有程序在运行的时候才被称为进程。一个程序运行完结束了之后,他就不叫进程了,也就无法通过此条命令被查询出来。

这样我们可以查到所有的进程。那么如何可以检索到某个特定的进程呢?

使用我们之前所学的管道和grep命令。

 ps axj | grep myprocess

通过管道和关键词检索的方式查出了指定进程,grep本身也是一个进程,所以也被检索出来了

那么每一列都是什么意思?我们可以通过下面这个命令把首行解释列出。

ps ajx | head -1

把首行解释和进程列表一起列出可以用&&来连接两条命令

ps ajx | head -1 && ps axj | grep process

首行解释和进程列表

pid

每个学生都有自己的学号,每个人都有自己的身份证号,每个进程也有自己唯一的标识符,叫做pid,进程PCB唯一区分用unsigned int pid

如果一个进程想要知道自己的pid该怎么办? task_struct被称为内核数据结构,pid就在里面,用户是不能直接访问内核数据结构的,应该通过操作系统为我们调用 task_struct的pid,例如 getpid()

process.c:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = getpid();while(1){printf("I am a process!,pid:%d\n",id);sleep(1);}return 0;
}

运行一下可以看到进程的pid: 

此时我们分屏打开第二个终端,在另一个终端中输入查找进程的指令,就可以确认该pid是不是进程pid

可以看到,确实是该进程的pid。

可能有些人理解不了,为什么要让一个程序一直在打印?因为一个程序一直在打印,才能说明他是一个正在运行的程序。我们输入命令没有反应的时候,就会通过新建一个终端来操作另一个终端。

在Windows下,我们启动一个进程,可以双击它的exe文件。在Linux下启动一个进程就是./执行可执行程序。

程序已经启动,我们可以用ps命令来查找进程。终止进程,我们可以使用ctrl+C,来让所有正在显示器进行的东西停止。

还有一计,我们可以通过在另一个终端上输入以下命令,就可以把另一个进程停止掉。

kill -9 pid

ppid

pid,我们知道是什么意思吧。那么ppid是什么意思呢? pid的其中一个p是process啊,ppid前面那个多出来的p的意思是parent,他的意思就是获取父进程的pid,对于子进程就是ppid

pid_t getppid(void)//获取ppid的函数
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = getpid();pid_t parent = getppid();while(1){printf("I am a process!,pid:%d ,ppid:%d\n",id,parent);sleep(1);}return 0;
}
ps ajx | head -1 && ps axj | grep process

还是这样查询

我们会发现每次启动一个程序时,它的pid都不一样,这很正常。因为每次一个程序运行时,操作系统会为他分发一个新的pid,然后在程序结束时回收这个旧的pid,下次运行时在随机分发一个新的pid,所以才每次不一样,不然你的进程不就可以随便被人锁定了吗?

顺便一提,关闭会话管理器可以在[查看]里重新开启。

我们知道pid是我们正在运行这个程序的身份号码?那么ppid是哪个程序呢?

我们的命令解释器(bash)就是父进程了。

每创建一个进程是否有代表操作系统里多了一个进程?是这样的,多了一个进程就相当于多了一份PCB和一套该进程对应的代码和数据。创建一套进程会创建它的PCB,也就是它的内核数据结构,可是用户是没有权限对内核数据结构进行增删改查的,用户不能直接创建一个task_struct,操作系统可以为用户提供系统调用。

是什么呢?

这里插播一下 Man手册的使用

有时候你在man手册里面查不到对应的库函数的时候,可能是因为你的man手册不全。你可以通过以下命令来安装。

sudo apt-get install manpages-posix-dev

但是我使用了之后发现,他告诉我命令不存在,于是我又查找了一下,最后发现我CentOS的系统中没有apt-get命令,需要从Yum中下载软件包,像这样

sudo yum install man-pages

于是你就可以下载更全面的man手册。

好了我们来查一下fork函数

fork除了有叉子的意思还有岔路分叉的意思。他的作用就是

创建一个进程。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>int main()
{printf("process is running,only me!\n");sleep(3);fork();printf("hello world\n");sleep(5);return 0;
}

实时检测一下

while :; do ps ajx | head -1 && ps axj | grep process | grep -v grep; sleep 1; done

刚开始是没有任何进程的,直到我们开始执行process.out这个程序之后,我们发现出现了第一个进程监控,它的pid是22655,这个22655,就是一开始的父进程。之后我们发现后面又出现了一个新的进程,它的pid是22705,而它的PPID是22655,也就是说这个新的进程是我们刚刚创建出来的进程的子进程。

那这个子进程是怎么来的?就是我们刚刚查出的fork函数创造出来的。fork的功能就是创造出一个子进程。 fork之后,系统中多了一个进程,也就是说,它多了一个task_struct的进程控制块。但是这个进程还要有自己的代码和数据才能组成一个PCB块,那么它的代码和数据从哪里来?父进程的代码和数据是从磁盘加载来的。默认情况下子进程继承父进程的代码和数据。所以子进程的PCB=自己的task_struct+父进程的代码+父进程的数据。

可以看到执行进程一个是父,一个是子 

主要是想让大家了解一下子进程执行和父进程执行不一样的代码,我们还需要学习一下fork的返回值。

pid_t fork(void);

如果fork函数成功了,它会返回子进程的pid给父进程,返回0给子进程;如果创建失败,则返回-1,错误码被设置,也就是说fork会返回两次,创建成功则给两方返回不同的值,不成功则都返回-1

来试一下

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>int main()
{printf("process is running,only me!,pid:%d\n", getpid());sleep(3);pid_t id = fork();if (id == -1) return 1;else if (id == 0){//childwhile (1){printf("id:%d,I am child process, pid: % d, ppid : % d\n",id,getpid(),getppid());sleep(1);}}else{//parentwhile (1){printf("id:%d,I am parent process,pid:%d,ppid:%d\n",id, getpid(), getppid());sleep(2);}}return 0;
}

这个代码的意思是,如果我们用id来接收fork的返回值,如果id是-1,就返回1,程序结束;如果id等于0,就进入while循环,如果id不等于1,也不等于0,就进入另一个while循环。

我们发现代码在fork之后进入了两个while循环,我们一般情况下只会进入一个while循环,不会出现两个while循环同时跑的情况(我们之前学习的都是单进程多进程情况也一样)

id为什么会进入两个while循环呢?

因为我们的for函数会有两个返回值且返回两次。它是由操作系统(OS)提供的,有自己的实现逻辑。当我们的函数执行到return的时候,这是否意味着函数的核心工作已经完成了?也就是说,当fork函数开始思考返回什么的时候,是不是意味着它已经解决了新建子进程的工作(无论他新建是否成功)

那么我们就应该return代码。 fork内部前半部分由父进程创建,子进程执行到return的时候我们的子进程已经创建完成了,父进程本来就在,这时候就有两个进程了,他们的代码共享。父进程执行一次return,子进程执行一次return。

但是我们同时进入了两个while循环,这就意味着我们的id大于0又等于零吗?

并不是,我们的子进程在创建的时候有自己的PCB,这个PCB在数据结构的层面与父进程的PCB是并列的,也就是说他们两个都在以同一条链子上串着,没有谁高谁低之分;但是在逻辑上是存在父子关系的。

父进程的代码和数据是从磁盘上加载来的。子进程继承父进程的代码和数据,子进程也要用父进程的代码和数据。

也就是说,从fork之后,我们这个代码被分为了两块并行,一块是给父进程执行的,一块是给子进程执行的。而这个ID他在父进程返回的值和在紫禁城返回的值也是不一样的,他们的变量名都叫ID,但是内容不一样。

一次创建5个进程

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<unistd.h>void RunChild()
{while (1){printf("I am parent, pid :%d,ppid: %d\n", getpid(), getppid());sleep(1);}
}
int main()
{const int num = 5;//创建五个子进程,改成几就创建几个子进程int i = 0;for (i = 0; i < num; i++){pid_t id = fork();if (id == 0)      //因为父进程的id不等于0,所以直接跳过判断执行下次循环,创建紫禁城{RunChild();}sleep(1);}while (1){sleep(1);printf("I am parent, pid :%d,ppid: %d\n", getpid(), getppid());}return 0;
}

进程创建可以用代码的方式:fork(),而不是每次都需要./来启动进程

查看进程

除了ps命令,进程还可以通过/proc系统文件夹查看(proc是根目录下的一个文件夹):

要获取PID为1021的进程信息,你需要查看/proc/1021这个文件夹,其中目录是以进程的pid命名的: 

ll /proc/29129

我们的进程都存在这里,都以进程的pid为目录名存在proc这个文件夹里。如果想要获取pid为1的进程信息,可以查看这个文件夹。

我们去7705里看看

其中一个比较重要的属性是exe。

这个exe是由绿色路径下的可执行程序加载出来的,如果我们把后面绿色路径这个可执行的程序干掉的话,程序也能跑。这是为什么?因为文件能够被继承调度是因为它在内存里。他为什么在内存里?是因为它从磁盘里加载出来。如果你在磁盘层面上把它删掉的话,内存里它还有还是可以跑的,只不过你重新关开关机之后可能就用不了了。

当然也有可能跑不了,比如说电脑内存只有8g,可执行程序占磁盘16G,你把这个16g删了,进程可能运行着运行着就出现问题了。

进程的PCB会记录自己对应的可执行程序路径。那么上面的cwd是什么呢?

cwd(current work dir)进程的当前工作路径

每个进程在启动时会记录自己在哪个路径下启动,也就是进程当前的路径。如果我们在代码里写一个新建文件的代码,它会在当前的工作路径下新建。

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{chdir("/root/ice");//这行是改变工作路径FILE *fp = fopen("log.txt","w");(void)fp;   //ignore warnning   fclose(fp);while(1){printf("I am a process,pid:%d\n",getpid());sleep(1);}return 0;
}

可以发现路径发生改变了: 

6cb0adb02d2e4eb1bdd86872c3f64b2f.png

 也确实在我们指定的目录下新建文件了:

6e8ff49902a049288cefc65b22841a87.png

 进程状态

每个进程都要有自己的状态

Linux进程状态

进程状态是task_struct内部的一个属性:

#define RUN 1
#define SLEEP 2
#define STOP 3struct task_struct
{//内部属性int status;}struct task_struct process1;
process1.status = RUN;

Linux改变一个进程的状态就是在改变task_struct的内部属性 (定义出的标志位,为了表示进程的状态)

Linux内核中对于状态有什么定义呢?

可以看看kernel的源码:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
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运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列

 S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

怎么看我们的进程是什么状态呢?

ps aux / ps axj 命令

首先先看一下关于makefile的骚操作:

myprocess:myprocess.cgcc -o $@ $^
.PHONY:clean
clean:rm -f myprocess

这是一段已经写好的makefile,怎样可以快速改变依赖文件呢?

输入

%s/Temp.out/code.out/           

更改完成

监视进程:

while :; do ps ajx | head -1 && ps ajx | grep process| grep -v grep; sleep 1; done

这个进程在持续输出

尽管它这么努力,但是他的状态还是Sleep(后面的加号等会说)

但是我们换成这个代码(注释掉printf)

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{while(1){//      printf("I am a process\n,pid:%d\n",getpid());}return 0;
}

经过监控我们会发现状态变成R(run)运行中了

R表示进程正在运行,可是printf不应该也在运行吗,为什么说我在Sleep?

首先我们明确一点:printf打印是往显示器上打印,程序是在远隔万里的服务器上跑的(因为我们是云服务器)但是打印的结果却返回到了近在咫尺的显示器,根据冯·诺依曼体系架构,显示器是一个外设,所以CPU在跑的时候,一直要先把数据写到内存里(像缓存)再刷新到外设上,那我能保证每次打印的时候显示器都是就绪的么?

程序是CPU在跑,CPU的速度可比显示器快多了,所以进程在被调度时需要访问显示器资源,大部分时间都在等待设备(也就是显示器)资源就绪,只要没就绪,就处于S状态,CPU执行(ns),程序打印(ms),所以我们在查询时可能查到的时候程序都是处于等待显示器资源的状态而并非运行状态,这也是为什么把printf注释掉之后就一直是R状态(可想而知差距有多大才会在刷屏的时候依旧查到很多S)

休眠状态就是进程在等待(硬件)资源就绪的过程

如何看到一个进程在休眠?

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{while(1){sleep(10);printf("I am a process\n,pid:%d\n",getpid());}return 0;
}

 上面的进程就是在休眠,使用Ctrl+C可以把进程终止

S这种睡眠是可以被中断的睡眠,这样可以让进程在后台运行:

 ./testStatus &

在后端运行的进程休眠S则不带+了 ,带不带+就是区分进程在前台和后台运行,在后台运行的进程是没法Ctrl+C的,只能kill了:

kill -9 2988

我们的进程有不同的状态,有一个状态叫T即停止状态,很有意思的点就在于我们可以通过一个进程控制其他进程:

kill -l

这是很多信号,我们曾经用过的信号-9就是杀掉进程,使用kill可以向指定信号发信号。

信号就是后面的数字,发信号时也有自己对应的名字,可以看到成功将进程暂停了

其实我们之前也做过让进程暂停的操作,这个操作叫做调试(在断点处不就停下来了吗,悟了没)

为了让进程可以被调试,修改下makefile文档:

process:process.cgcc -o $@ $^ -g
.PHONY:clean
clean:rm -f process

顺便一提这个的意思是要在每个动作行之前必须加一个<tab>

查一下我们debug信息

readelf -S process-debug | grep  debug

进入调试:gdb process-debug

 我们开启监控会发现查不到:

这个正在跑的是gdb,是我们正在调试,并不是我的进程

查找一下:

ps ajx | grep process-debug

我们却可以查到我们的进程

其实是因为我们开启的是调试,没有让进程跑起来

我们在代码内设置一个断点,来让进程跑起来

本来是s突然变成t了,这是个什么情况?

t表示当前进程因被追踪而暂停,遇到断点进程即暂停

D状态是Linux系统中比较特有的一种进程状态,名为磁盘暂停状态

操作系统在开机时就在内存中存在着,即开机时操作系统就被从磁盘加载到内存

假设这样的场景:内存中1GB的数据要存到磁盘中(把数据交给外设)。

进程在等外设写数据,所以当前的进程就处于一个S状态(休眠),等待写入完毕

Linux操作系统有权利杀掉进程来释放空间(内存严重不足)

磁盘不仅要加载这个进程,还要加载别的进程,忙了一圈发现进程被操作系统删掉了。。。。。。

为了防止出现这种数据丢失的情况,规定操作系统执行这样的法则:凡是在进行数据IO的进程一律不允许删!

在传数据的时候,进程要变为D状态:不可被杀!

D状态就是一种睡眠状态:深度睡眠:不可中断睡眠

那怎样结束D状态呢?让它自己醒过来呀!

还可以重启,如果重启都不管用就只能断电了(重启有时也会将数据向磁盘内刷新,如果不成功卡死就只能断电了)

僵尸进程和孤儿进程

进程退出的时候不是干巴巴的退出,是要把退出信息保存到进程的PCB中的,如果没有人读取PCB中进程退出的消息,那进程就一直不释放(代码和数据释放掉,PCB一直存在,直到等待,否则一直处于僵尸状态)

所以一个进程退出的时候并不会直接退出,而是会先处于一个僵尸状态,需要父进程对其回收。如果父进程不对其进行回收,那么对应的进程将一直处于僵尸状态

来试验一下

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = fork();if (id == 0){//childint cnt = 5;//五次之后,子进程退出while (cnt){printf("I am a child,cnt:%d,pid:%d\n", cnt, getpid());sleep(1);cnt--;}}else{//parentwhile (1){printf("I am a parent,running always!,pid:%d\n", getpid());sleep(1);}}return 0;
}

上面的进程退出后,子进程将处于僵尸状态,监控一下发现确实是这样:

Z状态表示已经运行完毕,但是需要维持自己的退出信息,在自己的进程task_struct会记录自己的退出信息,未来让父进程进行读取(要让父进程知道他为什么退出),如果没有父进程读取,僵尸进程会一直存在

进程 = 内核数据结构task_struct(需要被回收) + 进程的代码(被释放)+数据(被释放)

僵死状态(Zombies)是一个比较特殊的状态

当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

僵死进程会以Z(终止状态)保持在进程表中,并且会一直在等待父进程读取退出状态代码

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

kill是不能干掉僵尸进程的,它已经死了,你这个顶多算鞭尸

谈完僵尸进程,谈谈孤儿进程:

 如果子进程先退出,子进程的PCB等待回收,那么子进程成为僵尸进程;父进程先退出,我们把子进程叫做孤儿进程,孤儿进程一般都是会被1号进程(OS本身)进行领养的

首先我们知道了fork的原理了哈:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{pid_t id = fork();if(id == 0){//childwhile(1){printf("I am a child,pid:%d\n",getpid());sleep(1);}}else{//parentint cnt = 5;while(cnt--){printf("I am a parent,cnt:%d pid:%d\n",cnt,getpid());sleep(1);}}         return 0;
}

 孤儿进程为什么要被OS领养?因为我们依旧要保证子进程正常被回收,防止内存泄漏

那我们之前的僵尸进程怎么办?

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>int main()
{int cnt = 5;while(cnt){sleep(1);printf("I am a process,pid:%d\n",getpid());cnt--;}return 0;
}

我们会发现,这也不异常啊,不是很快自己就退下了?

主要是因为,命令行中启动的所有进程的父进程都是Bash,Bash会自动回收新进程的僵尸问题。我们之前那些退下的进程其实就相当于被bash回收了

进程的阻塞、挂起、运行

刚刚我们学了:R,S,D,T,X,其中下图的终止状态包含:Z,X

操作系统这门学科是所有操作系统的共性(指导思想),而Linux操作系统是操作系统思想的具体实现

​
struct PCB
{//所有属性struct PCB* next;//内存指针(指向自己的代码和数据)//一个进程一个PCB//进程=代码、数据+PCB
}

有很多进程想在CPU上运行,一个CPU配套一套运行队列(一条链表),运行队列包含对应的指针,对应的进程的task_struct被维护到了CPU的运行队列中(寄存器里),进程在运行队列中,它的状态就是R状态

就绪状态和运行状态可以大致被划分到一起,可以随时被调度

一个进程一旦持有CPU,会一直运行到这个进程结束么?

并不会,不然一个程序岂不是可以永无止境的运行了。它是基于时间片(一个单位)进行轮转调度的,每个进程的时间片结束如果进程没有运行完的话会被操作系统从CPU上剥离下来,这个时间片特别小,我们两脚兽是察觉不到的,但是当你的网站加载页面卡住的时候,很可能就是一个进程的其中一个时间片没结束

这是让多个进程以切换的方式进行调度,在一个时间段内得以推进代码,这个过程就叫做并发

Linux不是这样调度的(上面的只是调度算法的一种,比较简单)

任何时刻,都有多个进程在同时运行,我们叫做并行

阻塞态是一种比较重要的状态,重在理解

举个栗子:在C阶段的时候,scanf输入的话如果不给输入它会在那等着,这个状态怎么说呢?

#include<stdio.h>
int main()
{int a = 0;scanf("%d", &a);printf("%d", a);return 0;
}

进程阻塞在这里,等待键盘资源是否就绪,键盘上有没有被用户按下的按键,有的话将按键数据交给进程

操作系统是软硬件资源的管理者(进程本身是软件),操作系统是如何对硬件进行管理的呢?

#define KEY_BOARD 1
#define SCREEN 2
struct device
{int type;int status;//其他属性struct device* next;};

等待意味着进程并没有被调度,即没有在运行队列中,也就是在阻塞

#define KEY_BOARD 1
#define SCREEN 2
struct device
{int type;int status;//其他属性struct device* next;task_struct* wait_queue;
};

换个角度想其实我觉得更像在缓冲区,当键盘没被检测到数据的时候,进程被操作系统从CPU剥离下来放到磁盘的等待队列中,直接从运行状态转变为阻塞状态

不是只有CPU有运行队列,各种设备也有自己的wait_queue

阻塞和运行的状态变化,往往伴随着PCB被连入不同的队列中,入队列的不是进程的代码和数据,而是进程的task_struct

挂起态

磁盘里有个分区叫swap,一般大小是内存的1.5倍或者和内存一样大,内存中有很多进程(task_struct和代码及数据),有的时候OS内存十分吃紧,一个进程处于阻塞态就意味着当前进程不会被访问,代码和数据也并不会被访问,所以此时内存中的代码和数据就被换出到磁盘的swap分区中,会腾出可观的内存,当进程需要被进行则再进行换入的过程,这个过程进程还存在,进程的task_struct还在内存中,这个状态被称作阻塞挂起态,可以帮助操作系统更合理的使用内存资源

但频繁的换入换出会导致效率问题,进程调度周期变长(用时间换空间)

那么函数调用时候的返回值是如何返回的呢?

临时变量具有临时性,它的值通过寄存器返回。当进程被CPU调度时,寄存器会保存进程的临时数据。CPU所有寄存器中的临时数据,叫做进程的上下文,进程切换最重要的是上下文数据的保存和恢复

CPU内的寄存器:寄存器本身是硬件,具有数据的存储能力,CPU的寄存器硬件只有一套;但是CPU内部的数据可以有多套,有几个进程就有几套和该进程对应的上下文数据

寄存器≠寄存器的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400236.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker Hub 镜像代理加速

因为未知原因&#xff0c;docker hub 已经不能正常拉取镜像&#xff0c;可以使用以下代理服务来进行&#xff1a; "https://docker.m.daocloud.io", "https://noohub.ru", "https://huecker.io", "https://dockerhub.timeweb.cloud"…

更深层的理解视觉Transformer, 对视觉Transformer的剖析

写在前面&&笔者的个人理解 目前基于Transformer结构的算法模型已经在计算机视觉&#xff08;CV&#xff09;领域展现出了巨大的影响力。他们在很多基础的计算机视觉任务上都超过了之前的卷积神经网络&#xff08;CNN&#xff09;算法模型&#xff0c;下面是笔者找到的…

无字母绕过webshell

目录 代码 payload构造 php7 php5 构造payload 代码 不可以使用大小写字母、数字和$然后实现eval的注入执行 <?php if(isset($_GET[code])){$code $_GET[code];if(strlen($code)>35){die("Long.");}if(preg_match("/[A-Za-z0-9_$]/",$code))…

JavaEE 的入门

1. 学习JavaEE Java EE(Java Platform Enterprise Edition), Java 平台企业版. 是JavaSE的扩展, ⽤于解决企业级的开 发需求, 所以也可以称之为是⼀组⽤于企业开发的Java技术标准. 所以, 学习JavaEE主要是学习Java在 企业中如何应⽤. 前⾯学习的是Java基础, JavaEE 主要学习Jav…

easyExcel2.1.6自动trim()的问题

环境&#xff1a;easyExcel 2.1.6 问题&#xff1a;easyExcel会自动忽略String中的空格&#xff0c;调用trim()函数&#xff0c;导致excel中的空格失效。 代码如上所示&#xff0c;所以只需要把globalConfiguration的autoTrim()&#xff0c;设置为false即可 那么怎么设置confi…

【区块链+金融服务】河北股权交易所综合金融服务平台 | FISCO BCOS应用案例

区域性股权市场是我国资本市场的重要组成部分&#xff0c;是多层次资本市场体系的基石。河北股权交易所&#xff08;简称&#xff1a;河交所&#xff09; 作为河北省唯一一家区域性股权市场运营机构&#xff0c;打造河北股权交易所综合金融服务平台&#xff0c;将区块链技术与区…

Linux centos stream 9命令及源码

学过linux操作系统的人,对文件、命令比较熟悉。最多的操作是用命令处理文件。 随着学习的深入,会提出疑问:命令长什么样? 出于好奇,会找到命令存放的地方,用cat命令看一下,结果可想而知。 我们知道,命令分内部命令和外部命令,存放在不同的位置。外部命令就是一个可执…

OpenAI API error: “Unrecognized request argument supplied“

题意&#xff1a;OpenAI API 错误&#xff1a;‘提供了无法识别的请求参数’ 问题背景&#xff1a; Im receiving an error when calling the OpenAI API. Its not recognizing file argument, which I submitted to the API. 我在调用 OpenAI API 时遇到错误。API 不识别我提…

HikariCP连接池:Possibly consider using a shorter maxLifetime value.

相关的SQL总结&#xff1a; session级别&#xff1a; show variables like %timeout%; mysql的global级别&#xff1a; show global variables like %timeout%; # 对应 mysql 修改配置&#xff08;单位 秒&#xff09; set global wait_timeout300; set global interacti…

C++结构体指针强制转换以处理电力系统IEC103报文

前言 最近依旧是开发规约解析工具的103篇&#xff0c;已经完成了通用分类服务部分的解析&#xff0c;现在着手开始搞扰动数据传输&#xff0c;也就是故障录波的传输。 在103故障录波&#xff08;扰动数据&#xff09;的报文中&#xff0c;数据是一个数据集一个数据集地存放&a…

51单片机学习记录-数码管操作

这里实现了静态数码管的显示。51单片机一共有可以显示4个数字&#xff0c;可以通过控制P2(4-2)的端口选择8个数字显示器中的一个显示数字&#xff0c;控制P0端口写入显示的数值信息。将操作的逻辑使用了函数Nixie进行了封装。 #include <8051.h>unsigned char NixieTabl…

思科默认路由配置2

#路由协议实现# #任务二默认路由配置2# #1配置计算机的IP地址、子网掩码和网关 #2配置Router-A的名称及其接口IP地址 Router(config)#hostname Router-A Router-A(config)#int g0/0 Router-A(config-if)#ip add 192.168.1.1 255.255.255.0 Router-A(config-if)#no shutdow…

Selenium + Python 自动化测试07(滑块的操作方法)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 本篇文章主要讲述如何操作滑块。 目前很多系统登录或者注册的页面都有滑块相关的验证&#xff0c;selenium 中对滑块的基本操作采用了元素的拖曳的方式。需要用到Actiochains模…

应用兼容性问题-abi动态库错误分析和解决

1&#xff0c;应用名和现象 运行环境&#xff1a; 云手机 现象&#xff1a; 2&#xff0c;分析 --------- beginning of crash 08-14 03:59:59.014 28740 28740 E AndroidRuntime: FATAL EXCEPTION: main 08-14 03:59:59.014 28740 28740 E AndroidRuntime: Process: com.ks…

大型、复杂、逼真的安全服和安全帽检测:数据集和方法

智能升级工地安全&#xff1a;SFCHD数据集与SCALE模块介绍 在人工智能&#xff08;AI&#xff09;技术飞速发展的今天&#xff0c;其在建筑工地安全领域的应用正逐渐展现出巨大潜力。尤其是高风险行业如化工厂的施工现场&#xff0c;对工人的保护措施要求极为严格。个人防护装…

十四、迭代器模式

文章目录 1 基本介绍2 案例2.1 Aggregate 接口2.2 Iterator 接口2.3 MyArray 类2.4 MyArrayIterator 类2.5 Client 类2.6 Client 类的运行结果2.7 总结 3 各角色之间的关系3.1 角色3.1.1 Aggregate ( 集合 )3.1.2 Iterator ( 迭代器 )3.1.3 ConcreteAggregate ( 具体的集合 )3.…

Luminar Neo for Mac/Win:创新AI图像编辑软件的强大功能

Luminar Neo&#xff0c;这款由Skylum公司倾力打造的图像编辑软件&#xff0c;为Mac和Windows用户带来了前所未有的创作体验与编辑便利。作为一款融合了先进AI技术的图像处理工具&#xff0c;Luminar Neo以其独特的功能和高效的操作流程&#xff0c;成为了摄影师、设计师及摄影…

TPshop商城的保姆教程(Ubuntu)

1.上传TPSHOP源码 选择适合自己的版本下载 TPshop商城源文件下载链接&#xff1a; 百度网盘 请输入提取码 上传tpshop的源码包到特定目录/var/www/html 切换到/var/www/html 目录下 cd /var/www/html修改HTML目录下所有文件权限 chmod -R 777 * 2.打开网址配置 TPshop安…

第九届“创客中国”武汉区域赛正式启幕 灵途科技勇夺前三,晋级决赛!

8月8日&#xff0c;第九届“创客中国”武汉区域赛正式启幕&#xff0c;首场聚焦先进制造领域。灵途科技勇夺先进制造领域专场企业组前三名&#xff0c;成功晋级决赛。 “创客中国”大赛是工业和信息化部组织开展的双创赛事活动&#xff0c;以构建产业链协同发展为出发点&#…

鸿蒙(API 12 Beta3版)【扩展屏投播开发指导】使用投播组件

通过本节开发指导&#xff0c;可在系统镜像投屏后&#xff0c;获取投屏设备信息&#xff0c;实现扩展屏模式的投播&#xff0c;实现双屏协作的能力。 运作机制 虚拟扩展屏 是在系统投屏启动过程中建立的&#xff0c;依据双端协商的投屏视频流的分辨率创建&#xff0c;支持1080…