Linux——操作系统进程详解!!(建议收藏细品!!)

hello,大家好,这里是bang_bang,今天我们来讲进程,在讲进程之前,我们先来谈谈冯诺依曼体系结构和操作系统。

目录

 1 .冯诺依曼体系结构

 2 .操作系统(OS)

2.1 OS核心“管理”——先描述再组织

 3 . 进程

3.1 基本概念

3.2 描述进程——PCB

🍙task_struct

3.3 查看进程 

🍙通过系统调用获取进程标识符

🍙通过系统调用创建进程——fork初识

 4 .进程状态 

4.1 Linxu内核源代码

4.2 R运行状态(running)

4.3 S睡眠状态 (sleeping)

4.4 D磁盘休眠状态(Disk sleep)

4.5 T停止状态(stopped) 

🍙前台进程

🍙后台进程

4.6 X死亡状态(dead)

4.7 Z僵尸状态(Zombies) 

🍙僵尸进程的危害

 5 .孤儿进程


 1 .冯诺依曼体系结构

存储器:内存
输入设备:键盘,摄像头,话筒,磁盘,网卡...
输出设备:显示器,音响 ,磁盘,网卡...
中央处理器(CPU):运算器(算术运算,逻辑运算)、控制器(CPU是可以响应外部事件,协调外部就绪事件,比如:拷贝数据到内存)

强调:

  • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)

        CPU读取数据(数据+代码),都是要从内存中读取。站在数据的角度,我们认为CPU不和外设直接交互。

  • 外设要输入或者输出数据,也只能写入内存或者从内存中读取

        CPU要处理数据,需要先将外设中的数据,加载到内存。站在数据的角度,外设直接只和内存打交道。

  • 所有设备都只能和内存打交道。

 2 .操作系统(OS)

概念:

        任何计算机系统都包含一个基本的程序集合,称为操作系统(OS),操作系统包括:

                  内核(进程管理,内存管理,文件管理,驱动管理)

                  其他程序(例如函数库,shell程序等待)

设计OS的目的:

        与硬件交互,管理所有的软硬件资源

          为用户程序(应用程序)提供一个良好的执行环境

2.1 OS核心“管理”——先描述再组织

总之OS的核心的就是“管理”

那么是如何管理的呢?我来举个例子🌰

在大学期间,相比大家都知道奖学金,那么我们是如何获得奖学金的呢?

        我们一定是发出国家奖学金申请给辅导员,然后辅导员上报给校长,校长根据学生的各项数据进行决策,再通知辅导员结果,然后辅导员去执行相应的手续,这一整个流程就决定了学生是否能获得奖学金。

        注意:学生就是我们的被管理者,校长是管理者,辅导员是执行者。

        问题:管理者是如何管理被管理者的?

        ——流程中,管理者和被管理者没有直接交互,而是管理者拿到被管理者的核心数据然后进行管理决策

映射到操作系统中,管理者相当于我们的OS,执行者相当于驱动程序,被管理者相当于硬件

通过上面这个生活例子,我们可以看出管理的本质是:先描述再组织

描述:Linux内核是用C语言写的,那么描述就是用struct结构体

组织:用链表或者其他高效的数据结构

操作系统对外提供服务是提供系统调用!!(system call)

system call的本质:用C语言提供的函数。

 3 . 进程

3.1 基本概念

        进程:可执行程序与管理进程需要的数据结构的集合。 

        在Linux下,运行一条命令,./XXX,运行的时候,其实就是在系统层面创建了一个进程!!!(加载到内存中经过CPU处理) ,未执行的程序只是通过了编译链接后生成一个可执行程序放在磁盘之中,也就是说程序的一个执行实例就是进程。

Linux是可以同时具有加载多个程序的,Linux是可能同时存在大量的进程在系统中的(软件角度:OS,硬件角度:内存)

Linux系统是如何管理大量的进程的?——先描述,再组织(是不是承上启下了,>滑稽脸<)

3.2 描述进程——PCB

PCB(进程控制块),其是一个struct结构体,进程的属性的集合

Linux下的PCB是task_struct

🍙task_struct

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

        对进程的管理,变成了对进程PCB结构体链表的增删改查!

        进程=对应的代码和数据+进程对应的PCB结构体!

组织进程 

所有运行在系统里的进程都以task_struct链表的形式存在内核里。

3.3 查看进程 

在Linux下想查看进程信息可以采用2种方式:

  • 直接进入/proc系统文件夹下查看

  • 使用ps用户级工具获取

通过ps axj | head -1 获取头部信息

通过 ps axj | grep 文件名 来查找进程

通过grep -v grep 不查找grep进程

🍙通过系统调用获取进程标识符

  • 进程id(PID)
  • 父进程id(PPID)

使用系统接口getpid()获取子进程id,getppid()获取父进程id

获取进程PID:
getpid()
杀掉进程(信号):
$kill -9 进程PID
父进程永远是bash(shell),命令行的命令永远是bash的子进程。
获取父进程PID:
getppid()

🍙通过系统调用创建进程——fork初识

  • 创建子进程:失败返回-1,成功返回子进程PID给父进程,0返回给子进程

  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝) ps:写时拷贝下一篇进程地址空间写
  • fork 之后通常要用 if 进行分流

 测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
  printf("我是父进程\n");
  pid_t id=fork();
  if(id<0)
  {
    perror("创建子进程失败\n");
    return 1;
  }
  else if(id==0)
  {
    while(1)
    {
       printf("我是子进程,pid:%d,ppid:%d\n",getpid(),getppid());
       sleep(1);
    }
  }
  else{
    while(1)
    {
       printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
       sleep(1);
    }
  }
  return 0;
}

结果:

fork之后是代码共享的 ,fork之后有2个执行流(if else if)

为什么给子进程返回0,给父进程返回子进程的pid(感性的认识)?

    父进程:子进程=1:n

    就好比一个家庭,一为父亲有多个孩子,那么叫这个孩子的时候一定是喊他的名字/小名,需要一个特定的id来进行区分,而这些孩子叫父亲却不需要取特别的别称,因为孩子只有1位父亲,所以fork后给父进程返回子进程的pid用来进行区分,给子进程返回0即可。

🧀问题:

  • 创建进程的时候,OS要做什么?

本质,就是系统多了一个进程——要新建一个task_struct,内部属性,要以父进程为模板。

操作系统和cpu运行某一个进程,本质从task_struct形成的队列中挑选一个task_struct,来执行它的代码!

进程调度,变成了在task_struct的队列中选择一个进程的过程!!

  • fork为什么会有2个返回值? 

1.因为fork内部,父子各自会执行自己的return语句

2.返回2次,并不意味着会保存2次

3.return 本质是写入,发生写时拷贝,所以父子进程各自其实在物理内存中,有属于自己的变量空间!只不过在用户层用同一个变量(虚拟地址!)来标识了。

  • 父子进程被创建出来,哪一个进程先运行呢? 

谁先运行,不一定,这个是由操作系统的调度器决定的! 

 4 .进程状态 

为了彻底弄明白正在运行的进程是什么意思,我们需要清楚知道进程的不同状态。

(1)运行态→阻塞态:进程发现它不能运行下去时发生这种转换。这是因为进程发生IO请求或等待某件事情
(2)运行态→就绪态:在系统认为运行进程占用 CPU的时间已经过长,决定让其他进程占用CPU 时发生这种转换。这是由调度程序引起的。调度程序是操作系统的一部分,进程甚至感觉不到它的存在。
(3)就绪态→运行态:运行进程已经用完分给它的CPU时间,调度程序从处于就绪态的进程中选择一个投入运行。
(4)阻塞态→就绪态:当一个进程等待的一个外部事件发生时(例如输人数据到达),则发生这种转换。如果这时没有其他进程运行,则转换(3)立即被触发,该进程便开始运行。
从操作系统的原理知道,进程一般有三种基本状态执行态、就绪态和等待态,但是在具体操作系统的实现中,设计者根据具体需要可以设置不同的状态。 

挂起:一种特殊的阻塞状态

当内存快不足的时候,操作系统将长时间不执行(闲置)的进程代码和数据置换到磁盘上,这种状态称作挂起!

4.1 Linxu内核源代码

kernel源代码定义如下:

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 */
}

4.2 R运行状态(running)

运行状态并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

我们来进行验证:这段代码感性的认识是不是应该已知执行,因为它在while循环里。

验证: 

事实上,我们通过ps工具能看到他是处于S(睡眠)状态。

这是为什么呢?

因为我们程序中使用了scanf函数, 请求外设输入,而CPU的处理语句速度又是十分迅速的,处理语句只花了几ms,而大部分的时间都在等待外设队列,所以显示出是S状态。

4.3 S睡眠状态 (sleeping)

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

小故事:一个进程将资源给磁盘进行处理,而磁盘的速度相比较是缓慢的,这时内存突然快不足了,OS就会将该进程调度到其他地方,磁盘处理完资源后,发现该进程不见了,这导致资源会丢失。为了解决这种情况,又设计出了D状态(不可中断睡眠状态)

4.4 D磁盘休眠状态(Disk sleep)

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

上面的小故事放在D状态中,该进程就不会被OS调度走,继续等待磁盘处理资源。

4.5 T停止状态(stopped) 

        可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。(只是停代码,调试过程)

信号(后面文章会讲):

🍙前台进程

PS循环查看进程状态语句:

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

前台进程:(+)前台进程启动,执行命令没有用,且能被ctrl+c结束,占用bash框

🍙后台进程

后台进程(&):不能被ctrl+c结束,使用kill杀掉进程

4.6 X死亡状态(dead)

        这个状态只是一个返回状态,你不会在任务列表里看到这个状态(瞬时性非常强)。

4.7 Z僵尸状态(Zombies) 

        僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。

 举个🌰:

        一在操场跑步,突然前面的人嘎了,你肯定会拨打110,🚔来了后肯定不是直接把他带走🔥,而是要等法医进行检查,判断死亡原因。

        在这个过程中,等待法医到来检查期间,就叫做僵尸状态。

一段创建僵尸进程的代码例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 1;
}
 else if(id > 0){ //parent
 printf("parent[%d] is sleeping...\n", getpid());
 sleep(30);
 }else{
 printf("child[%d] is begin Z...\n", getpid());
 sleep(5);
 exit(EXIT_SUCCESS);
 }
 return 0;
}

总结: 

  • 僵尸状态是什么?

一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态。

只要子进程退出(PCB保留,代码和资源可释放掉),父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

  • 为什么要有僵尸状态?

维持该状态,是为了让父进程或者OS来进行回收(Z->X)!

🍙僵尸进程的危害

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态

  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护

  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!

  • 内存泄漏?是的!

 5 .孤儿进程

 父进程退出,子进程还在,子进程就叫孤儿进程。孤儿进程会被领养,被1号进程领养(init,系统本身)

 我们还是用一段代码来测试一下:这段代码5s后,父进程被退出。

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

//孤儿进程
int main()
{
  pid_t id=fork();
  if(id==0)
  {
     //chiled
     while(1)
     {
         printf("I am child\n");
         sleep(1);
     }
  }
  else
  {
     //father
     int cnt=5;
     while(cnt)
     {
         printf("I am father:%d\n",cnt--);
         sleep(1);
     }
  }                                                                                   
  return 0;
}

为什么要被领养?

    未来子进程退出的时候,父进程早已不在,需要领养进程来进行回收


文末结语,本篇文章从最基础的冯诺依曼体系结构开始,一步步剖析操作系统的管理本质——先描述再管理,再承上启下讲解进程概念及其结构体task_struct和进程状态,以及特殊的僵尸进程和孤儿进程!!希望大家能有所收获! 

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值