Linux——进程概念详解

时间:2024-07-18 07:01:43

一、进程的基本概念

在给进程下定义之前,我们先了解一下进程:

我们在编写完代码并运行起来时,在我们的磁盘中会形成一个可执行文件,当我们双击这个可执行文件时(程序时),这个程序会加载到内存中,而这个时候我们不能把它叫做程序了,应该叫做进程。所以说,只要把程序(运行起来)加载到内存中,就称之为进程。

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

二、进程描述—PCB

PCB:进程控制块(结构体)
当一个程序加载到内存中,操作系统要为刚刚加载到内存的程序创建一个结构体(PCB),进程信息被放在这个结构体中(PCB),可以理解为PCB是进程的属性的集合。

在Linux操作系统下的PCB是:task_struct
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
在进程执行时,任意时间内,进程对应的PCB都要以下内容:

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

优先级的理解:
由于CPU只要一个,进程可以有多个,所以CPU的资源有限,操作系统在调度进程到CPU时会根据进程的优先级来判断。
程序计数器的理解:
CPU跑一个进程时,要执行它的代码,而代码是自上往下执行的(if、else、循环等除外),CPU先要取指令,然后分析指令,再然后执行指令。取完一个指令后,CPU中的寄存器(EIP指令寄存器)会保存当前指令的下一条指令的地址,方便下次取下一个指令。
所谓的函数跳转、分支判断、循环等,都是修改EIP完成的。
上下文数据的理解:
CPU在跑一个进程时,没有跑完就开始切换其他进程,为了下次继续跑完这个进程,会保留这个进程的上下文数据,当这个进程回来时,会把上下文数据移动到CPU内部继续执行。

三、通过系统调用获取进程标示符

#include<stdio.h>
#include<unistd.h>
int main()
{
   while(1)
   {
     printf("i am process...pid:%d,ppid:%d\n",getpid(),getppid());
     sleep(1);
   }
return 0;
 }           

通过ps aux | grep 文件名来找
其中getpid()是找进程的标示符,getppid()找父进程的标示符

四、通过系统调用创建进程——fork

fork函数可以创建一个子进程

#include<stdio.h>
#include<unistd.h>
int main()
{
  pid_t id=fork();//创建子进程
  while(1)
  {
    if(id==0)
    {
   		printf("i am process...child---pid:%d,ppid:%d\n",getpid(),getppid());
    	sleep(1);
    }
    else if(id>0)
    {
	     printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid());
	    sleep(1);
    }
    else{
    	;
    }
  }
 return 0;                                                                                  
}

我们可以得出:这段代码有两个进程,并且它们的关系是父子关系。

什么是fork函数:在调用fork函数之前,只有一个进程(父进程),当这个进程调用fork函数之后,fork函数会复制一个进程(子进程),区别是PID不同,它们的关系是父子关系。

fork函数会返回两次值:
——给父进程返回子进程的pid。
——给子进程返回0。
——失败时,在父进程中返回-1,不创建子进程,并且errno被适当地设置。

五、进程的状态

操作系统存在着五种状态模型:

  • 新建态:刚刚创建的进程,操作系统还没有把它加入可执行进程组中。
  • 就绪态:进程已经做好准备,只有有机会就会开始执行。
  • 运行态:该进程正在执行。
  • 阻塞态:进程在某些事件发生前不能执行,如I/O操作完成。
  • 退出态:操作系统从可执行进程组中释放出进程,或者自身停止,或者是因为某些原因被取消。

R 可执行状态

通过命令 ps aux 可以查看进程的状态

【注意】此可执行状态并非上面的运行态。
进程中的R状态不代表正在运行,代表的可被调度,此运行相当于上面的就绪态。

操作系统会把进程中R状态的进程全放在调度队列中,方便调度。

S 睡眠状态

(1)创建一个可执行态

#include<stdio.h>
#include<unistd.h>
int main() 
{
    while(1);
	return 0;
}

(2)创建休眠态进程

#include<stdio.h>
#include<unistd.h>
int main() 
{
    while(1)
    sleep(10);
	return 0;
}

S状态是浅度睡眠,随时可以被唤醒,也可以被杀掉。

D 磁盘休眠状态

可以表示为深度睡眠,该进程不会被杀掉,即使你是操作系统,除非我自动唤醒,才可以恢复。

这种状态(D)的进程杀不死。

T 暂停状态

向进程发送SIGSTOP信号,该进程会响应该信号进入暂停状态,
向该进程发送SIGCONT信号,该进程会从暂停状态恢复到可执行状态。

X 死亡状态 & Z 僵尸状态

僵尸状态:一个处于僵尸状态的进程,会等待它的父进程或操作系统对它的信息进行读取,之后才会被释放。

通过代码来模拟僵尸状态的进程:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t id=fork();
   	int count=5;
   	while(1)
  	{
     	if(id==0)
    	{
       		while(count){
     		printf("i am process..child---.pid:%d,ppid:%d\n,count: %d",getpid(),getppi    d(),--count);
    		sleep(1);
   			}
     		 printf("child quit....\n");
    		 exit(1);
   		}
    	else if(id>0)
     	{
     		printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid());
     		sleep(1);
   		}
  	}
 	return 0;
 }

 while :; do ps aux |head -1&&ps aux|grep a.out; echo "#######################";sleep 1;done 来监控进程的状态。

死亡状态:进程被操作系统释放了或者自己退出了

六、僵尸进程

当一个进程变为僵尸状态的时候,该进程就变成了僵尸进程。

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

僵尸进程的危害

七、孤儿进程

在Linux中,进程的关系主要是父子关系。

一对父子进程中的父进程退出了,子进程还在运行,就会形成孤儿进程。
如果没有进程来回收该子进程的信息,那么会变成僵尸状态,会存在内存泄漏的问题。

为了解决这个问题,该子进程会立即被1号init进程领养。

通过代码来模拟孤儿状态的进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t id=fork();
   	int count=5;
   	while(1)
  	{
     	if(id==0)
    	{
       		while(1){
     		printf("i am process..child---.pid:%d,ppid:%d\n,count: %d",getpid(),getppid(),--count);
    		sleep(1);
   			}
     		printf("child quit....\n");
    		exit(1);
   		}
    	else if(id>0)
     	{
            while(count)
            {
     	        printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid()); 
     		    count--;
                sleep(1);
            }
            exit(0);
   		}
  	}
 	return 0;
 }

用 while :; do ps axj |head -1 && ps axj | grep a.out;echo "#######################";sleep 1;done 来监控进程的状态。

观察子进程的PPID

通过查看该子进程的信息,可以得知该进程被1号init进程领养。

八、进程优先级

基本概念

CPU中的资源是有限的,不可能多个进程一起在CPU上运行,利用优先级把进程有效的先后排好,改善了系统的性能。

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)
  • 优先权高的有优先执行权。

查看进程优先级

用 ps -l 可以查看到进程的优先级

PRI:表示这个进程被执行的优先级,其值越小越早执行
NI:表示这个进程的nice值

PIR and NI

nice值表示进程可被执行的优先级的修正值。
PIR=PIR(old)+nice。
当nice为负值时,那么该进程的优先级值会变小,优先级会变高,进程越快被执行。

当然,nice也是有范围的,-20~19,一共40个级别。

用top命令更改已存在的进程的nice

top命令

接着按r然后输入进程的PID输入nice

其他

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