• 操作系统之进程与进程控制


    一、进程概念

    引子 程序运行在并发环境中的问题

    (1)运行过程不确定
    (2)结果不可再现

    1.进程定义

      进程是程序在某个数据集合上的一次运行活动。数据集合是指软硬件环境,多个进程共存或共享的环境。

    2.进程的特征

    (1)动态性
      进程是程序的一次执行过程,动态产生且动态消亡;
    (2)并发性
      进程同其他进程一起向前推进;
    (3)异步性
      进程按照各自的速度向前推进(每一个进程按照自定逻辑,不考虑其他进程的运行,各自占用CPU);
    (4)独立性
      进程是系统分配资源和调度CPU的单位(但是有了线程后,操作系统调度CPU的单位就变成了线程)。

    3.进程与程序的区别

    (1)进程是动态的:程序的一次执行过程;
    (2)程序是静态的:一组指令的有序集合;
    (3)进程是暂存的:在内存中短期驻留;
    (4)程序是长存的:可以在存储介质上长期保存;
    (5)一个程序可能有多个进程。

    4.进程的分类

    (1)按照使用资源的权限进行分类
      ①系统进程:系统内核相关的进程;
      ②用户进程:运行于用户态的进程。
    (2)按照对CPU的依赖性进行分类
      ①CPU型进程:主要用于计算;
      ②I/O型进程:主要用于I/O操作。

    二、进程状态

    1.进程的状态

    (1)运行状态(Running)
      进程已经占用CPU,在CPU上运行。
    (2)就绪状态(Ready)
      具备运行条件但是由于没有CPU可用,所以暂时不能运行。
    (3)阻塞状态(Block)也叫等待状态(Wait)
      由于等待某项服务完成或者等待某个信号而不能运行的状态,比如等待系统调用,I/O操作等等。

    2.进程的三态模型

    (1)就绪->运行:进程调度;
    (2)运行->就绪:时间片到或者被强占;
    (3)运行->阻塞:请求服务后等待响应,或者等待某个信号的到来;
    (4)阻塞->就绪:请求的服务已经完成,或者等待的信号已经到来。

    3.进程的五态模型

    (1)新建状态
      用户向系统提交程序后,在进程建立之前的过程。
    (2)终止状态
      进程撤出系统的过程。
    4.Linux中的状态定义
    (1)可运行态
      ①就绪:在就绪队列中等待调度;
      ②运行:正在运行。
    (2)阻塞(等待)态
      ①浅度(可中断)阻塞:能被其他进程的信号或者时钟唤醒;
      ②深度(不可中断)阻塞:不能被其他进程通过信号和时钟唤醒,一般是用来请求文件服务,I/O服务,系统服务。
    (3)僵死态
      进程终止运行,释放大部分资源。
    (4)挂起态
      进程被挂起,暂停。可用于调试进程。

    三、进程控制块(Process Control Block,PCB)

    1.进程控制块的定义

    (1)描述进程状态、资源、与相关进程关系的数据结构;
    (2)PCB是进程的标志。对于操作系统来说,它通过PCB来感知和管理进程;
    (3)进程创建时会建立PCB,进程撤出时会销毁PCB。

    2.PCB中的基本成员

    (1)name(ID):进程名称(标识符,0~32768的正整数);
    (2)status:状态;
    (3)next_pcb:指向下一个PCB的指针;
    (4)start_addr:程序地址;
    (5)p_priority:优先级;
    (6)cpu status:现场保留区(堆栈);
    (7)comminfo:进程通信;
    (8)processfamily:家族;
    (9)own resource:资源。
      不同的操作系统PCB成员会有所区别,但是一些基本成员都是相同的,可能只是命名上的区别而已。进程控制块应包含以下三类信息:标识信息、现场信息、控制信息。

    3.Linux中的进程控制块

    Linux的进程控制块一般包含如下信息:
    (1)进程状态;
    (2)调度信息;
    (3)标识符;
    (4)内部进程通信信息;
    (5)链接信息;
    (6)时间和计时器;
    (7)文件系统;
    (8)虚拟内存信息;
    (9)处理器信息。

    4.进程的切换

    (1)进程的上下文

      Context,指进程运行环境,CPU环境(比如各个寄存器的取值)等等。进程的上下文由三部分组成:用户级上下文(程序、数据、共享存储区、用户栈,它们占用进程的虚拟地址空间)、寄存器上下文(由各个寄存器组成)、系统级上下文(PCB、核心栈等)。
    (2)进程切换过程
      ①进程被从堆栈调度到CPU运行;
      ②进程被从CPU调度到堆栈暂停运行。

    四、进程控制的概念

    1.进程控制的概念

      在进程生存期间,对其全部行为的控制。典型的控制行为有创建进程、阻塞进程、唤醒进程、撤销进程。

    2.进程控制行为之一——进程创建

    (1)功能
      创建一个具有指定标识的进程。
    (2)参数
      进程标识、优先级、进程起始地址、CPU初始状态、资源需求等等。
    (3)创建进程的过程
      ①创建一个空白PCB;
      ②获得并赋予进程标识符ID;
      ③为进程分配空间;
      ④初始化PCB;
      ⑤把进程插入到相应的进程队列,一般放到就绪队列中。

    3.进程控制行为之二——进程撤销

    (1)功能
      撤销一个指定的进程,收回进程所占用的资源,撤销该进程的PCB。
    (2)进程撤销的时机/时间
      ①正常结束;
      ②异常结束;
      ③外界因素。
    (3)参数
      被撤销的进程名(ID)。
    (4)进程撤销的过程
      ①在PCB队列中找到要撤销进程的PCB;
      ②获取该进程的状态;
      ③若该进程处于运行态,则立即终止该进程(先检查该进程是否有子进程,若有子进程,则先撤销子进程,递归执行此操作);
      ④释放进程占有的资源;
      ⑤将进程的PCB从PCB队列中移除。

    4.进程控制行为之三——进程阻塞

    (1)功能
      阻塞进程的执行。
    (2)阻塞的时机/事件
      ①请求系统服务:操作系统不能立即满足进程的请求,导致进程不能满足运行条件;
      ②启动某种操作:进程启动某操作,阻塞等待该操作完成(比如进行I/O操作);
      ③新数据尚未到达:该进程要获得另外一个进程的中间结果,该进程需要等待另一个进程运算结束;
      ④进程正常结束:进程完成任务后,自我阻塞,等待新任务到达。
    (3)参数
      阻塞原因。
    (4)进程阻塞的实现
      ①停止运行;
      ②将PCB“运行态”改为“阻塞态”;
      ③出入相应原因的阻塞队列;
      ④转到调度程序(把系统控制权交给调度模块)。

    5.进程控制行为之四——进程唤醒

    (1)功能
      唤醒处于阻塞队列中的某个进程。
    (2)引起唤醒的时机/事件
      ①系统服务满足进程要求;
      ②I/O事件完成;
      ③新数据到达;
      ④进程向系统或I/O提出新请求(服务)。
    (3)参数
      进程ID。

    6.进程控制原语

      由若干指令构成的具有特定功能的函数,具有原子性,其操作不可分割。以上四种进程控制行为(创建、撤销、阻塞、唤醒)都被封装为原语,以保证其运行的完整性。

    五、Linux下的进程控制

    1.创建进程

      创建进程的函数原型是pid_t fork(void);
      pid_t是一个整数类型,即fork()函数会返回新进程的ID号(0~32768的整数)。
      例如:pid_t pid = fork();

    注意:
    (1)新进程是当前进程的子进程。
    (2)父进程和子进程
      ①父进程:fork()的调用者;
      ②子进程:新建的进程。
    (3)子进程是父进程的复制(相同的代码,相同的数据,相同的堆栈),除了ID号和时间信息外,两者完全相同。
    (4)子进程和父进程可以并发运行。

    请问下面的程序会输出什么结果?

    //文件名为test.c
    int main(void)
    {
        fork();
        printf("Hello World!
    ");
    
        return 0;
    }

    答案:

      

      一个是子进程输出的,一个是父进程输出的。

    请问下面的程序会输出什么结果?

    //文件名为fork_2.c
    int main(void)
    {
        pid_t pid;
        pid = fork();
        
        if(pid == 0)
        {
            printf("pid == 0
    ");
        }
        else
        {
            printf("pid != 0
    ");
        }
    
        return 0;
    }

    答案:
      

      为什么if和else两个分支都被执行了呢?

      因为fork()函数执行后,创建了一个新的进程,子进程是父进程的复制,所以相同的代码会执行两次。在子进程中fork()函数会返回0,在父进程中,fork()函数会返回子进程的id号。所以在子进程中,会执行if分支,在父进程中,会执行else分支。但谁先谁后不确定。

    2.fork()函数的执行步骤

      由于子进程是父进程的复制,所以子进程中也会有创建子进程的语句,如果不加以限制,就会形成递归创建,但实际上并不是这样的。
      实际流程是:父进程创建了子进程后,子进程中“创建进程”语句及其前面的语句都不再执行。并发运行的是fork()语句后的语句。

       

    在linux的源码中我们可以找到fork函数:

    //linux内核中的fork函数
    ...
    copy_files(clone_flags,p);    //克隆文件
    copy_fs(clone_flags,p);    //克隆文件系统
    copy_mm(clone_flags,p);    //克隆内存信息
    ...

      我们可以看到有三条语句,用于拷贝进程的所有信息,这也解释了为什么说子进程是父进程的复制。

    3.子进程如何执行与父进程不同的功能。

      Linux中 ,init进程(初始化进程)是所有其他进程的父进程,那么是不是就说所有的进程都执行与init进程相同的功能呢?并不是。Linux中某些子进程和父进程的执行并不是完全相同的。它是如何做到的呢?这是因为exec函数簇的存在,它是若干函数的集合。其功能是让子进程具有和父进程完全不同的新功能。

    4.一个进程控制的实例

      编写一个代码,创建两个子进程,第一个子进程打印“I am the 1st subprocess!”,第二个子进程打印“I am the 2rd subprocess”,父进程打印“I am the parent process”。要求实现先打印第一个子进程的内容,再打印第二个子进程的内容,最后打印父进程的内容。

     1 int main()
     2 {
     3   int p1, p2; //进程ID
     4  
     5   p1 = fork();
     6   switch(p1)
     7   {
     8     case -1:  //进程创建失败时执行下列语句,因为进程创建失败fork函数会返回-1
     9         printf("Error
    ");  
    10         exit(1);
    11     case 0:  //子进程中执行下列语句,因为在子进程中fork函数会返回0
    12         execl("/bin/echo","echo", "I am the 1st subprocess!", NULL);  //使用exec函数簇中的execl函数将子进程一的功能进行改造
    13         exit(1);
    14     default:  //父进程中执行下列语句,因为在父进程中fork函数会返回子进程的ID号(大于0,小于32768)
    15         wait(NULL);  //用于进程同步,wait函数的作用是:父进程在此处暂停运行,等待一个子进程结束后,再从此处继续向下运行
    16         break;
    17   }
    18  
    19   p2 = fork();
    20   switch(p2)
    21   {
    22     case -1:  //进程创建失败时执行下列语句,因为进程创建失败fork函数会返回-1
    23         printf("Error
    ");
    24         exit(1);
    25     case 0:  //子进程中执行下列语句,因为在子进程中fork函数会返回0
    26         execl("/bin/echo", "echo", "I am the 2rd subprocess", NULL);  //使用exec函数簇中的execl函数将子进程二的功能进行改造
    27         exit(1);
    28     default:  //父进程中执行下列语句,因为在父进程中fork函数会返回子进程的ID号(大于0,小于32768)
    29         wait(NULL);  //用于进程同步,wait函数的作用是:父进程在此处暂停运行,等待一个子进程结束后,再从此处继续向下运行
    30         break;
    31   }
    32  
    33   printf("I am the parent process
    ");
    34  
    35   return 0;
    36 }

    运行结果:

      

  • 相关阅读:
    linux 中的vim的配置文件的位置
    centos find
    multi-cursor
    ctrlsf插件
    Vim的可视模式
    Vim的tagbar插件
    Vim的tag系统
    ~/.ctag的作用与配置
    在Vim里使用gtags-cscope
    查看Vim的option变量的值
  • 原文地址:https://www.cnblogs.com/UnfriendlyARM/p/10111083.html
Copyright © 2020-2023  润新知