【Linux详解】——进程概念

📖 前言:本期对计算机的软硬件体系结构进行梳理,包括计算机体系结构,什么是操作系统,为什么存在操作系统,操作系统如何进行管理,以及建立在这些软硬件基础上的各种提供给用户进行操作的接口。对于理解操作系统本身以及下一节的进程概念,甚至对整个Linux系统编程的理解都有着至关重要的作用。


🕒 1. 冯诺依曼体系结构

在这里插入图片描述

  • 其中运算器、控制器,再加上其他的一些寄存器统称为中央处理器 (CPU);
  • 存储器一般指内存,内存掉电易失,只能用作临时存储;
  • 输入设备和输出设备被统称为外设,其中磁盘、网卡等属于输入输出设备,键盘只属于输入设备,显示器只属于输出设备,磁盘是不带电存储的,可以用于永久存储。

CPU只能被动的接受别人传递过来的数据和指令,然后将运算得到的结果返回。

那么这里就会出现两个问题:
1、CPU如何能够识别我们传递给它的数据和指令?
答案是CPU内部有一套自己的指令集,它会把指令对应到指令集,然后完成相应的操作;其中CPU的指令集是二进制的,这就是为什么我们编写的代码需要经过编译链接变成二进制的可执行程序后才能被运行的原因
– CPU需要读懂我们的指令才能完成对应的运算;这也是编译器存在与产生的根本原因。

2、CPU需要的数据从哪里获取? 答案是内存
(此处不考虑缓存);虽然我们的数据是存放在磁盘中的,但是由于磁盘读取与写入数据的速度太慢了 –
可以简单理解为CPU的运算速度以纳秒为单位,内存的运算速度以微秒为单位,而磁盘的运算速度则是以毫秒甚至秒为单位;CPU如果直接从磁盘中读取和写入数据,会有大量的等待时间,所以为了提高计算机的整体效率,CPU只会向内存中读取和写入数据;内存再向磁盘中读取和写入数据;其中,内存向磁盘读取和写入数据就是IO的过程。

经过上面的学习,我们可以得到如下结论:

在数据层面上,CPU不会直接和外设打交道,而只会和内存打交道;同样,所有的外设需要载入数据时,只能载入到内存,内存要写入数据,也只能写入到外设中。

有了上面的知识铺垫后,我们就可以解释为什么 程序运行必须加载到内存 中了 – CPU需要从程序中读取数据,但是CPU只和内存打交道,而我们的程序是存储在磁盘中的。

最后,我们对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,以下面这个例子为例:我们在QQ聊天窗口向别人发送一条消息到别人接受消息的过程中,数据的流动过程?如果发送的是文件呢?

  • 发送消息 --> 接受消息 的数据流动:A 的键盘 --> A的内存 --> A的CPU (加密) --> A的网卡 --> B的网卡 --> B的内存 --> B的CPU (解密) --> B的显示器;
  • 发送文件 --> 接受文件 的数据流动:A 的磁盘 --> A的内存 --> A的CPU (加密) --> A的网卡 --> B的网卡 --> B的内存 --> B的CPU (解密) --> B的磁盘;

🕒 2. 操作系统(OS)

🕘 2.1 概念

操作系统是一个进行软硬件资源管理的软件。
在这里插入图片描述
在这里插入图片描述

🕘 2.2 操作系统如何进行管理

  1. 管理者不需要与被管理者直接交互,依旧能够很好的将被管理者管理起来(校长与学生);
  2. 管理的本质是对数据进行管理(综测与学业分思想分);
  3. 管理的方法是先描述,再组织(利用面向对象的思想看待学生);

🕘 2.3 系统调用接口

用户访问软硬件的需求,比如从磁盘中读取与写入数据、向显示器打印数据、通过网卡发送数据等;操作系统会给用户提供系统调用的接口,即当用户有访问软硬件的需求时,直接调用操作系统提供的接口,然后由操作系统来帮助用户完成对应的工作;这样即满足了用户的需求,又保护了软硬件资源。避免恶意操作(如删除磁盘驱动、向磁盘中添加恶意数据等等;)

注:Linux 操作系统是Linus Torvalds于1991年使用C语言编写的,而上述的各种系统调用接口又是由操作系统提供的,所以它们也是C式的接口,说白了就是 用C语言编写的用于用户调用的各种函数接口。

🕘 2.4 用户操作接口

虽然操作系统为我们提供了各种系统调用接口让我们来访问软硬件,但是这些接口在使用上功能比较基础,对用户的要求也相对较高;于是人们在系统调用接口的基础上开发出了用户操作接口,比如 Linux 下的外壳程序 shell,各种函数库 (C/C++等),windows 图形化界面 (GUI),以及一些指令 (编译好的可执行程序) 等;

用户通过这些操作接口进行指令操作、开发操作以及管理操作等等;比如 Linux 下外壳程序 bash 提供的 ls指令,本质上是调用系统接口,将磁盘中文件信息写入到显示器;touch 本质是调用系统接口,在磁盘上创建文件;又比如 C++的 cin/cout 函数,底层都是调用系统调用接口从键盘读入数据/向显示器上打印数据。

🕒 3. 进程

🕘 3.1 概念

进程:一个运行起来(加载到内存)的程序

进程 = 进程对应的磁盘代码 + 进程对应的内核数据结构(PCB)(process control block)
有了进程我们就要进行管理,即 PCB (进程控制块),在Linux下就是 task_struct

在这里插入图片描述

task_ struct 内容分类

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

🕘 3.2 查看进程

Windows下:
在这里插入图片描述
Linux下:
在这里插入图片描述
我们写个程序观察下进程

# Makefile
myproc:myproc.c
	gcc -o myproc myproc.c
.PHONY:clean
clean:
	rm -f myproc
//myproc.c
#include <stdio.h>
#include <unistd.h>
int main()
{
	while(1)
    {
  		printf("I am a process.\n");
        sleep(1);
    }                                
    return 0;
}

查看指定进程并显示标题:ps ajx | head -1 && ps ajx | grep 'myproc'(注意:-1是提取第一行)
在这里插入图片描述
关闭进程:kill -9 + 标识符PID
在这里插入图片描述
我们给myproc.c加点东西,演示下系统调用

#include <sys/types.h>
...
printf("I am a process.My ID is: %d\n",getpid());   
...

在这里插入图片描述
进程其实也是文件
在这里插入图片描述
删除进程的可执行文件会怎么样呢
在这里插入图片描述

🕘 3.3 常见进程标识符

🕤 3.3.1 进程id(PID)与父进程id(PPID)

[hins@VM-12-13-centos test_process]$ ./myproc
I am a process.My ID is: 10772,PPID is: 30650
I am a process.My ID is: 10772,PPID is: 30650
^C
[hins@VM-12-13-centos test_process]$ ./myproc
I am a process.My ID is: 10785,PPID is: 30650
I am a process.My ID is: 10785,PPID is: 30650
^C
[hins@VM-12-13-centos test_process]$ ./myproc
I am a process.My ID is: 10791,PPID is: 30650
I am a process.My ID is: 10791,PPID is: 30650
^C
[hins@VM-12-13-centos test_process]$ ps ajx | head -1 && ps ajx | grep 30650
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30650 10806 10806 30650 pts/1    10806 R+    1001   0:00 ps ajx
30650 10807 10806 30650 pts/1    10806 S+    1001   0:00 grep --color=auto 30650
26536 30650 30650 30650 pts/1    10806 Ss    1001   0:00 -bash

发现PID一直在变,而PPID就不变,当退出服务器再重新登录后,PPID才会变

命令行上启动的进程,一般它的父进程没有特殊情况的话,都是bash!

程序运行都是子进程负责,原因是一旦程序出错,父进程可以及时收到错误信息并反馈给用户,而不是直接挂掉。

🕤 3.3.2 通过系统调用创建进程(fork)

int main()
{
  // 创建子进程 -- fork是一个函数。 函数执行前:只有一个父进程;函数执行后:父进程+子进程
  pid_t id = fork();                                                        
  printf("I am a process.My ID is: %d,PPID is: %d, id:%d\n",getpid(),getppid(),id);    
  sleep(2);                                                                   
  return 0;                                                 
}
[hins@VM-12-13-centos test_process]$ ./myproc
I am a process.My ID is: 15734,PPID is: 30650, id:15735	# 父进程
I am a process.My ID is: 15735,PPID is: 15734, id:0		# 子进程

我们发现,同一个变量id,在后续不会被修改的情况下,竟然有不同的内容。
下面再给代码加个判断观察一下

int main()
{
  	// 创建子进程 -- fork是一个函数。 函数执行前:只有一个父进程;函数执行后:父进程+子进程
 	pid_t id = fork();   
	if(id == 0)
    {
        //子进程
        while(1)
        {
            printf("子进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
            sleep(1);
        }
    }
    else if(id > 0)
    {
        // parent
        while(1)
        {
            printf("父进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
            sleep(2);
        }
    }
    else{  }
    return 0;                                                 
}
[hins@VM-12-13-centos test_process]$ ./myproc
父进程, pid: 17106, ppid: 30650, id: 17107
子进程, pid: 17107, ppid: 17106, id: 0
子进程, pid: 17107, ppid: 17106, id: 0
父进程, pid: 17106, ppid: 30650, id: 17107
子进程, pid: 17107, ppid: 17106, id: 0
子进程, pid: 17107, ppid: 17106, id: 0
父进程, pid: 17106, ppid: 30650, id: 17107
子进程, pid: 17107, ppid: 17106, id: 0
子进程, pid: 17107, ppid: 17106, id: 0
父进程, pid: 17106, ppid: 30650, id: 17107
......

fork() 之后,会有父进程+子进程两个进程在执行后续代码
fork后续的代码,被父子进程共享!
通过返回值不同,让父子进程执行后续共享的代码的一部分!
这就是多进程。

🕘 3.4 进程状态

有了PCB结构体和数据结构,很大程度上能够提升管理进程的效率,但远远不够。我们需要将进程进行分类,我们引入状态这个概念。通过状态。每个进程都有自己的状态,这些状态能够告诉操作系统我正在干什么、我将要干什么,也就是说,进程的多种状态,本质都是为了满足未来的某种使用场景。

在这里插入图片描述
状态有:运行、新建、就绪、挂起、阻塞、等待、停止、挂机、死亡

  1. 一个CPU一个运行队列
  2. 让进程入队列,本质:将该进程的task_struct 结构体对象放入运行队列中!
  3. 进程PCB在runqueue,就是R,不是这个进程正在运行,才是运行状态。
  4. 不要只意味,你的进程只会等待(占用)CPU资源,你的进程,也可能随时随地,要外设资源。
  5. 所谓的进程不同的状态,本质是进程在不同的队列中,等待某种资源!

在这里插入图片描述

那么我们接下来着重介绍的便是三种状态:

  • 运行状态:即就是cpu正在处理的进程。具体点,进程的PCB结构体正在cpu的运行队列上排队
  • 阻塞状态:每种硬件都有一个等待队列,那么进程的PCB结构体被操作系统放在这个等待队列时,这个进程就处于阻塞状态,通常也称等待状态。
  • 挂起状态:内存满负荷时,又要增加新的进程显然是不行的。所以操作系统会观察内存中的哪些进程没有被放在任何一个队列里面(在内存里面啥也不干),找到以后就把此进程的代码和数据短期内置换到磁盘上,仅保留此进程的PCB。腾出的这一块空间供新的进程使用。这个动作,就叫做挂起。

实际上,不同的进程状态,其本质就是处于不同的队列。

🕤 3.4.1 Linux内核的描述

/*
* 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 (Sleep):该进程目前正在睡眠状态,但可以被唤醒。因而也称为浅度睡眠。
  • D:不可被唤醒的睡眠状态,通常这个进程可能在等待I/O的情况。因而也称为深度睡眠。无法被OS杀掉只能通过断电或者进程自己醒来解决。
  • T:停止状态,发送一个暂停信号给进程,进程就暂停了。
  • t:追踪停止状态,通常在断点调试时,进程处于此状态。
  • X(dead):死亡状态,这个状态是用来告诉操作系统的,所以我们观察不到此状态。
  • Z (Zombie):僵尸状态,进程已经死亡,但是却无法被删除至内存外。

那么想要在Linux环境下观察进程的状态,我们可用的指令有两个:

  • ps aux 或者 ps axj 查看系统所有的进程
  • ps -lA 也是能够查看系统的所有进程

在这里插入图片描述

// 测试代码
while(1)
  {
    int cnt = 0;
    int a = 0;
    a = 1 + 1;       
    printf("当前a的值是: %d, running flag: %d\n", a,  cnt++);
  }     

在这里插入图片描述
原因就在于这个printf,它打印到显示器上(外设),而硬件都比较慢,等显示器就绪,要花比较长的时间(CPU)
因此可能有99%的时间在等IO就绪,1%时间在执行代码

在这里插入图片描述
暂停状态可以理解为阻塞状态的一种,但是否是挂起我们不确定

[hins@VM-12-13-centos test_process]$ kill -19 9900	#暂停
[hins@VM-12-13-centos test_process]$ ps axj | head -1 && ps axj | grep myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
  690  9900  9900   690 pts/0      690 T     1001   0:29 ./myproc
 3926 10022 10021  3926 pts/1    10021 S+    1001   0:00 grep --color=auto myproc
[hins@VM-12-13-centos test_process]$ kill -18 9900	#继续
[hins@VM-12-13-centos test_process]$ ps axj | head -1 && ps axj | grep myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
  690  9900  9900   690 pts/0      690 R     1001   0:31 ./myproc
 3926 10587 10586  3926 pts/1    10586 R+    1001   0:00 grep --color=auto myproc
[hins@VM-12-13-centos test_process]$ kill -9 9900	#完全停止
[hins@VM-12-13-centos test_process]$ ps axj | head -1 && ps axj | grep myproc
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 3926 10809 10808  3926 pts/1    10808 S+    1001   0:00 grep --color=auto myproc
 #状态后带+是前台进程,不带+的是后台进程,不能Ctrl + C关掉进程

🕤 3.4.2 僵尸进程

一个进程被创建出来,完成任务后,要知道它完成的如何,则进程退出时不能立即释放该进程对应的资源!而是保存一段时间后,让父进程或者OS来进行读取,这个状态就是僵尸状态,读取后释放内存,这个进程才是死亡状态

模拟僵尸状态
创建子进程,让父进程不要退出,而且什么都不做,让子进程正常退出。

# Makefile
myprocess:myprocess.c
	gcc -o $@ $^ -g
.PHONY:clean
clean:
	rm -f myprocess
#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
int main()  
{  
  pid_t id = fork();  
  if(id == 0)  
  {  
     //child  
     printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());     
     sleep(5);
     exit(1);
  } 
  else   
  {   
      //parent  
      while(1)  
      {  
           printf("I am parent proceass, pid: %d, ppid: %d\n", getpid(),  getppid()); 
           sleep(1);  
      } 
  }                                                                      
  return 0; 
}

查看进程:ps ajx | head -1 && ps ajx | grep 'myprocess' | grep -v grep(不显示grep进程)
编写一个每1s就显示进程的脚本:while :; do ps ajx | head -1 && ps ajx | grep 'myprocess' | grep -v grep; sleep 1; done

[hins@VM-12-13-centos test_process_2]$ ./myprocess
I am parent proceass, pid: 15603, ppid: 9091	
I am child process, pid: 15604, ppid: 15603	 	
I am parent proceass, pid: 15603, ppid: 9091	# 1s
I am parent proceass, pid: 15603, ppid: 9091	# 2s
I am parent proceass, pid: 15603, ppid: 9091	# 3s
I am parent proceass, pid: 15603, ppid: 9091	# 4s
I am parent proceass, pid: 15603, ppid: 9091	# 5s
PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 1s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 2s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 3s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 4s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 5s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 Z+    1001   0:00 [myprocess] <defunct>
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	# 6s
 9091 15603 15603  9091 pts/0    15603 S+    1001   0:00 ./myprocess
15603 15604 15603  9091 pts/0    15603 Z+    1001   0:00 [myprocess] <defunct>

在这里插入图片描述
ptrace系统调用追踪进程运行,有兴趣研究一下

僵尸进程的危害

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  • 内存泄漏?是的!
  • 如何避免?后面讲

🕤 3.4.3 孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init进程领养,当然要有init进程回收喽。

模拟孤儿状态

#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
int main()  
{  
  pid_t id = fork();  
  if(id == 0)  
  {  
     //child  
     while(1)
     {
     	printf("I am child process, pid: %d, ppid: %d\n", getpid(), getppid());     
     	sleep(1);
     }
  } 
  else   
  {   
      //parent  
      while(1)  
      {  
           printf("I am parent proceass, pid: %d, ppid: %d\n", getpid(),  getppid()); 
           sleep(1);  
      } 
  }                                                                      
  return 0; 
}
I am parent proceass, pid: 21726, ppid: 9091
I am child process, pid: 21727, ppid: 21726
Killed	# 父进程被干掉
[hins@VM-12-13-centos test_process_2]$ I am child process, pid: 21727, ppid: 1
I am child process, pid: 21727, ppid: 1
I am child process, pid: 21727, ppid: 1
PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 9091 21726 21726  9091 pts/0    21726 S+    1001   0:00 ./myprocess
21726 21727 21726  9091 pts/0    21726 S+    1001   0:00 ./myprocess
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND	#父进程被干掉
    1 21727 21726  9091 pts/0     9091 S     1001   0:00 ./myprocess
 # 1号进程就是操作系统,现在这个子进程被1号进程“领养”了,那么这个进程就是孤儿进程,且会自动变成后台进程
 # 如果不领养,对应的僵尸进程,就没有人能回收了。
[hins@VM-12-13-centos test_process_2]$ kill -9 21726	# 杀掉父进程

可以看到干掉父进程后,父进程没有出现僵尸状态。这是为什么呢?
其实父进程也有父进程,即爷爷进程,而bash就是这个爷爷进程,它会帮你把这个父进程回收。

🕘 3.5 进程优先级

🕤 3.5.1 基本概念

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

🕤 3.5.2 查看系统进程

ps -l

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001 32079  9091  0  80   0 -  1053 hrtime pts/0    00:00:00 myprocess
0 R  1001 32139 21153  0  80   0 - 38332 -      pts/2    00:00:00 ps
  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

最终优先级 = 老的优先级(80) + nice值

PRI and NI

  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高
  • 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,一共40个级别。

PRI vs NI

  • 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
  • 可以理解nice值是进程优先级的修正修正数据

🕤 3.5.3 更改优先级的命令

用top命令(可能需要sudo)更改已存在进程的nice:进入top后按“r”–>输入进程PID–>输入nice值
在这里插入图片描述

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001   655  9091  0  60 -20 -  1054 hrtime pts/0    00:00:00 myprocess
0 R  1001  1453 21153  0  80   0 - 38332 -      pts/2    00:00:00 ps

🕤 3.5.4 其他概念

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发(一段时间采用:时间片轮转的方式)

🕘 3.6 进程切换(并发)

cpu中有一个eip寄存器(PC指针),指向下一条指令的地址。

进程切换的理解: 由于Linux的CPU一次只能运行一个进程,但是我们一个时间段内却可以运行多个进程,因为CPU是足够快的,因此我们人感觉的一瞬间就相当于CPU的一个时间段,想一想1ms对于人来说算是一瞬间,但是CPU却是以纳秒为单位计时的,因此在我们自身感觉到的一瞬间也就是CPU的一个时间段内,会将执行的多个进程按照一定的周期分别运行,一个运行到固定周期之后就强行拉入运行队列的末尾等待,就这样直到完成所有执行的进程,这就是进程之间在一定的时间内相互切换,叫做进程切换。而所谓的周期就是时间片。(并发中提到)

进程的上下文保护
当CPU在进行进程切换的时候,要进行进程的上下文保护,当进程在恢复运行的时候,要进行上下文进程的恢复!

上下文是什么呢?
首先进行感性的理解:当你由于应征入伍离开学校,保留学籍的过程即上下文保护;回到学校重新开始学习生活,恢复学籍的过程即上下文恢复。
进程在运行时会产生非常多的临时数据,同时CPU中存在一套寄存器硬件,当进程运行时,进程的PCB会被放入CPU内的寄存器中,此时CPU就可以通过进程PCB(暂时理解成PCB)得到进程代码数据的地址;CPU在运行进程时所产生的大量的临时数据也都会被保存在寄存器中;因此在进行进程切换时需要进行进程的上下文保护与上下文恢复,进程停止运行时将寄存器里面的数据保存起来,进程重新运行时将保存的数据再放入到寄存器中;所以进程的上下文就是一个进程完成他的时间片后所保存的数据

注:寄存器硬件 != 寄存器内的数据
进程在运行的时候,占有CPU,进程不是一直要占有到进程结束!如while(1)

CPU寄存器硬件被所有进程共享,但CPU在具体运行某一进程时,CPU寄存器中的数据只属于该进程;同时,我们进行上下文保护时保存的是寄存器中的数据,而不是寄存器硬件。


OK,以上就是本期知识点“进程概念”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值