先了解一下这篇的基础知识
操作系统简述-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;
}
可以发现路径发生改变了:
也确实在我们指定的目录下新建文件了:
进程状态
每个进程都要有自己的状态
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内部的数据可以有多套,有几个进程就有几套和该进程对应的上下文数据
寄存器≠寄存器的内容