第三章读书笔记

第三章读数笔记

Unix/Linux进程管理

3.1多任务管理

  • 一般来说,多任务处理指的是同时进行几项独立活动的能力。在计算机技术中,多任务处理指的是同时执行几个独立的任务。在单处理器(单CPU)中,一次只能执行一个任务,多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。这种逻辑并行性被称为“并发”。在多处理器(多核CPU)中,同时执行多个任务这种并行性称为“并行”。

3.2进程的概念

  • 在操作系统种中,任务也称为进程,与程序不同,程序是静态的,而进程是动态的。在实际应用中,进程和任务这两个术语可以互换。
  • 进程的定义:进程是对映像的执行。
  • 进程的数据结构:进程控制块(PCB)或任务控制块(TCB),称为PROC结构体。一个简单的PROC结构体如下:
  • 单CPU系统中,操作系统内核经常会使用PROC指针,指向当前正在执行的PROC。
  • 多CPU的多处理操作系统中,可在不同的CPU上实时、并行执行多个进程。因此多处理器系统中正在运行的[NCPU]可能是一个指针数组,每一个元素指向一个正在特定CPU上运行进程。

3.3多任务处理系统

多任务处理系统MT编程示例如下(示例代码过长,在此不予展示):

  • type.h文件:定义了系统常数和表示进程的简单PROC结构体。
  • ts.s文件:在32位GCC汇编代码中可实现进程上下文切换。
  • queue.c文件:可实现队列和链表操作函数。
  • t.c文件:t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。

3.4进程同步

  • “并发”执行时协调各进程正确有序地执行。
  • 最简单的进程同步工具:休眠唤醒操作。

3.4.1休眠操作

  • 休眠条件:当某个进程申请资源或等待事件发生却没能得到“满足”时,该进程就会在某个事件值上进入休眠状态,,该事件值表示休眠原因。
  • 休眠操作实现:可在PROC结构体中添加一个event字段,并实现ksleep(int event)函数(当event参数满足休眠条件时,执行休眠操作)。
  • 修改后的PROC结构体如下:
  • ksleep(int event)函数算法如下:
点击查看代码
int kwait(int *status)
{
    if(caller has no child)return -1 for error;
    while(1){//caller has children
        search for a (any) ZOMBIE child;
        if(found a ZOMBIE child){
            get ZOMBIE child pid
            copy ZOMBIE child exitCode to *status;
            burry the ZUMBIE child(put its PROC back to freeList)
                return ZOMBIE child pid;
        }
        ksleep(running);
    }
}
###3.4.2唤醒操作 - 当某一个等待的时刻到来时,kwakeup()函数将被调用以唤醒等待这一时间值的所有程序,值得一提的是被唤醒的进程不会被允许立刻申请资源,而是一个一个排队等待到来的资源。 - kwakeup()函数算法如下:

3.5进程终止

  • 正常终止:进程调用exit(value)。发出-exit(value)系统调用来执行在操作系统内核中的kexit(value)。
  • 异常终止:进程因为某个信号而异常终止。
    以上二者均会调用kexit()函数。

2.5.3等待进程中止

任何时候,进程都可以调用内核函数
pid =kwait(int *status)
等待僵尸子进程。如果成功,则返回的pid是僵尸子进程的pid,而status包含僵尸子进程的退出代码。此外,kwait()还会将僵尸子进程释放回freeList以便重用。kwait的算法如下:

点击查看代码
int kwait(int *status)
{
    if(caller has no child)return -1 for error;
    while(1){//caller has children
        search for a (any) ZOMBIE child;
        if(found a ZOMBIE child){
            get ZOMBIE child pid
            copy ZOMBIE child exitCode to *status;
            burry the ZUMBIE child(put its PROC back to freeList)
                return ZOMBIE child pid;
        }
        ksleep(running);
    }
}

3.7Unix/Linux中的进程

进程来源

  • 操作系统启动时,内核会强制创建一个PID=0的初始进程,通过分配PROC结构体(PROC[0])进行创建,初始化PROC内容,并让运行指向proc[0];系统执行初始化进程P0;大多数操作系统都以这种方式开始第一个进程。P0继续初始化系统,包括系统硬件和内核数据结构。挂载一个根文件系统,使系统可以使用文件。初始化系统后,P0复刻出一个子进程P1,并把进程切换为用户模式运行P1。

INIT和守护进程

  • P1通常被称为INIT进程,因为他的执行映像是init程序。
  • 守护进程:在后台运行,不与任何用户交互

登录进程

  • 每个LOGIN进程打开三个与自己的终端相关联的文件流,这三个文件流分别是用于标准输入的stdin、标准输出的stdout、用于标准错误消息的stderr。每个文件流都是指向进程堆栈区中FILE结构体的指针。

sh进程

  • 用户登录成功时,LOGIN进程会获取用户的gid和uid,从而成为用户的进程。它将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序sh(通常称为sh进程)。它提示用户执行命令。一些特殊的命令,如成cd(更改目录)、退出、注销等,由sh自己直接执行。其他大多数命令是各种bin目录中的可执行文件。
    对于每个(可执行文件)命令,sh会复刻一个子进程,并等待子进程终止。子进程将其执行映像更改为命令文件并执行命令程序。子进程在终止时会唤醒父进程sh,父进程会收集子进程终止状态、释放子进程PROC结构体并提示执行另一个命令等。除简单命令外,sh还支持I/O重定向和通过管道连接的多个命令。

进程的执行模式

  • Unix/Linux中,进程以两种不同的模式执行,即内核模式和用户模式,简称Kmode和Umode。
  • 在进程的生命周期中,会在Kmode和Umode之间发生多次迁移。每个进程都在Kmode下产生并开始执行。
  • 在Kmode下执行所有相关操作,包括终止。在Kmode下通过将CPU状态寄存器从K模式更改为U模式,可以轻松切换到Umode。但是进入Umode就不能够随意更改CPU状态了。Umode进程只能通过下面三种方式进入Kmode:

(1)中断:外部设备发送给CPU信号,请求CPU服务。当在Umode下执行时,CPU中断是启用的,因此它将响应任何中断。中断发生时,CPU将进入Kmode处理中断,这将导致进程进入Kmode;
(2)陷阱:陷阱是错误条件,错误条件被CPU识别为异常,使得CPU进入Kmode来处理错误。在Unix/Linux中内核陷阱处理程序将陷阱原因转换为信号编号,并将信号传递给进程。对于大多数信号,进程的默认操作是终止。
(3)系统调用(syscall):允许Umode进程进入Kmode以执行内核函数的机制。当某进程执行完内核函数后,它将期望结果和一个返回值返回到Umode,0表示成功,1表示错误。发生错误,外部全局变量errno(在errno.h中)会包含一个ERROR代码,用于标识错误。

3.8进程中的系统调用

  • fork()函数
    Usage: int pid = fork();
    fork()创建子进程并返回该子进程的Pid
    fork()函数可能有三种不同的返回值: 1.在父进程中,fork返回新创建子进程的进程ID; 2.在子进程中,fork返回0; 3.如果出现错误,fork返回一个负值;
  • sleep()函数:是进程进入休眠状态
  • exit()函数:执行一些清理工作,如刷新stdout,关闭I/O流等,然后终止进程;也可以先不执行而直接关闭进程
  • wait()函数:系统调用,,等待僵尸子进程,若成功则返回其Pid;还可以释放僵尸子进程以供重新使用
  • exec()函数:更改进程执行映像

3.9I/O重定向

3.9.1文件流和文件描述符

  • sh进程有三个用于终端I/O的文件流:stdin(标准输入)、stdout(标准输出)、stderr(标准错误)。其文件描述符分别对应0、1、2。其结构体如下:

3.10管道

  • 管道是用于进程交换数据的单向进程通信通道。管道有一个读取端和一个写入端。可以从管道的读取端读取写入端的数据。目前有普通管道和双向管道两种,双向管道是数据可以双向传输的管道。

  • 命令管道又叫FIFO:

    (1)在sh中,通过mknod命令创建一个命令管道:

    (2)或在c语言中发出mknod()系统调用

    (3)进程可像访问普通文件一样发个文命名管道。

实践内容

fork()函数示例:

  • 代码如下:
点击查看代码
#include <unistd.h>
#include <stdio.h>
int main ()

{

    int  pid; 
    
    
    printf("this is %d my parent=%d\n",getpid(),getppid());
    pid = fork();
     if (pid)
    {
      printf("i am the process %d, child=%d\n", getpid(),pid);
       
    }
    else
    {
      printf("i am the process %d, parent=%d\n", getpid(),getppid());
    }
    return 0;
}
- 文中给出的代码中有一处错误


“CHILD PID=d\n” 应为 “CHILD PID=%d\n”

示例3.2实践

  • 书中代码如下:
点击查看代码
#include <stdio.h>
int main()
{
    int pid=fork(); // fork a child
    if (pid)
    {
        printf("PARENT %d CHILD=%d\n", getpid(), pid);
        //sleep(1);
        printf("PARENT %d EXIT\n", getpid());
    }  
        
    else
    {   
        printf("child %d start my parent«%d\n", getpid(), getppid());
        // sleep(2);    // sleep 2 seconds -> let parent die first
        printf("child %d exit my parent=%d\n", getpid(), getppid());
    }
}

  • 1.取消注释sleep(1) 结果

  • 2.取消注释sleep(2) 结果

  • 3.取消注释sleep(1)和sleep(2) 结果

  • 可知2与3的打印间隔时间是一样的,与书中所述情况一致。

示例3.3实践

  • 书中代码如下:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int pid, status;
    pid = fork();
    if (pid)
    {
        printf("PARENT %d WAITS FOR CHILD %d TO DIE\n", getpid(),pid); 
        pid = wait(&status)>    // wait for ZOMBIE child process
        printf("DEAD CHILD=%d, status=0x%04x\n", pid, status);
    } // PARENT:
    else
    {
        printf("child %d dies by exit(VALUE)\n", getpid());
        exit(100);
    }
}

-实践结果:

  • 子程序终止状态为0X6400,与书中一致。

示例3.4实践

  • 书中代码如下:
点击查看代码
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <sys/prctl.h>
​
int main()
{
    int pid,r,status;
    printf("mark process %d as a subreaper\n",getpid());
    r = prctl(PR_SET_CHILD_SUBREAPER);
    pid = fork();
    if(pid)
    {
         printf("subreaper %d child = %d\n", getpid(), pid);
        while (1)
        {
             pid = wait(&status);
            if (pid > 0)
            {
                printf("subreaper %d waited a ZOMBIE=%d\n",getpid(), pid);}
                else
                     break;
            
        }
    }
    else
    {
        printf("child %d parent = %d\n", getpid(), (pid_t)getppid);
        pid = fork();
        if (pid)
        {
            printf("child=%d start: grandchild=%d\n", getpid(),pid);
            printf("child=%d EXIT: grandchild=%d\n",getpid(),pid);
        }
        else
        {
            printf("grandchild=%d start:myparent=%d\n",getpid(),getppid());
            printf("grandcild=%d EXIT:myparent=%d\n", getpid(),getppid());
        }
    }
}
  • 实践结果如下:
posted @ 2022-10-07 14:41  给我个名字  阅读(16)  评论(0编辑  收藏  举报