• 第三章读书笔记


    第三章读数笔记

    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());
            }
        }
    }
    
    • 实践结果如下:
  • 相关阅读:
    border-color
    animation
    servlet injection analysis
    事务隔离级别
    write RE validation
    Filter
    Annotation
    Injector
    gradle的安装
    Build tool
  • 原文地址:https://www.cnblogs.com/wdys12138/p/16759704.html
Copyright © 2020-2023  润新知