• 第三章学习总结


    第3章 Unix/Linux进程管理

    1 知识点归纳

    1.1 多任务处理

    一般来说,多任务处理指的是同时进行几项独活动的能力。在计算机技术中,多任务处理指的是同时执行几个独立的任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行任务从一个任务切换到另一个任务。

    1.2 进程的概念

    进程的正式定义:进程是对映像的执行。

    一个简单的PROC结构体:

    typedef struct proc{
        struct proc *next;	 // next proc pointer
        int *ksp; 			 // saved sp: at byte offset 4
        int pid; 			 // process ID                          
        int ppid; 			 // parent process pid     
        int status; 		 // PROC status=FREE|READY, etc.  
        int priority; 		 // scheduling priority  
        int kstack[1024]; 	 // process execution stack  
    }PROC;
    

    在PROC结构体中:

    • next是指向下一个PROC结构体的指针,用于在各种动态数据结构(如链表和队列)中维护PROC结构体。ksp字段是保存的堆栈指针。
    • pid是标识一个进程的ID编号,
    • ppid是父进程ID的编号,
    • status是进程的当前状态,
    • priority是进程调度优先级,
    • kstack是进程执行的堆栈。

    1.3 Unix/Linux中的进程

    1.3.1 进程来源

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

    1.3.2 INIT和守护进程

    当进程P1开始运行时,它将其执行镜像改为INIT程序。因此,P1通常被称为INIT进程,因为它的执行映像是init程序。P1开始复刻出许多子进程。P1的大部分子进程都是用来提供系统服务的。它们在后台运行,不与任何用户交互。这样的进程称为守护进程。守护进程的例子有:

    syslogd :log daemon process
    inetd  	:Internet service daemon process
    httpd 	:HTTP server daemon process
    etc.
    
    1.3.3 登录进程

    除了守护进程之外,P1还复刻了许多LOGIN进程,每个终端上一个,用于用户登录。每个LOGIN进程打开三个与自己的终端相关联的文件流,这三个文件流是用于标准输入的stdin、用于标准输出的stdout和用于标准错误消息的stderr。每个文件流都是指向进程堆区中FILE结构体的指针。每个FILE结构体记录一个一个文件描述符(数字),stdin的文件描述符是0,stdout的是1,stderr的是2。然后,每个LOGIN进程向stdout显示一个

    login:
    

    以等待用户登录。

    1.3.4 sh进程

    当用户成功登录时,LOGIN进程会获取用户的gid和uid,从而成为用户的进程。它将目录更改为用户的主目录并列执行出的程序,通常是命令解释程序sh。现在,用户进程执行sh,因此用户进程通常称为sh进程。

    1.3.5 进程的执行模式

    Unix/Linux中,进程以两种不同的模式执行,即内核模式和用户模式,简称Kmode和Umode。如图3.4所示。

    在进程的生命周期中,会在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代码,用于标识错误。

    1.4 进程管理的系统调用

    在本节中,我们将讨论Linux中与进程管理相关的以下系统调用:

    fork(),wait(),exec(),exit()
    

    每个都是发出时间系统调用的库函数:

    int syscall(int a,int b,int c,int d);
    

    其中,第一个参数a表示系统调用号,b、c、d表示对应核函数的参数。

    1.4.1 fork()函数
    Usage: int pid = fork();
    

    fork()创建子进程并返回该子进程的Pid,如果fork()失败则返回-1。

    
    #include <unistd.h>
    #include <stdio.h>
     
    int main ()
    {
        pid_t fpid; 
    	printf("this is %d my parent = %d\n", getpid(),getppid());
        fpid = fork();
        if (fpid < 0)
            printf("error in fork!");
        else if (fpid == 0)
        {
            printf("i am the child process, my process id is %d/n", getpid());//返回调用进程的PID
        }
        else
        {
            printf("i am the parent process, my process id is %d/n", getpid());//返回调用进程的PID
        }
        return 0;
    }
    
    1.4.2 进程执行顺序

    在fork()完成后,子进程与父进程和系统中所有其他进程竞争CPU运行时间。接下来运行哪个进程取决于它们的调度优先级,优先级呈动态变化。
    下面的示例演示了进程可能的各种执行顺序。

    #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.4.3 等待子程序终止

    在任何一个时候,一个进程都可以使用

    int pid = wait(int *status);
    

    系统调用,等待僵尸子进程。如果成功,则wait()会返回僵尸子进程的PID,而且status包含僵尸子进程的的exitCode()。此外,wait()还会释放僵尸子进程,以供重新使用。

    #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);
        }
    }
    
    1.4.4 Linux中的subreaper进程

    自内核3.4版本以来,Linux处理孤儿进程的方式略有不同。进程可以用系统调用将自己定义为subreaper进程:

    prctl(PR_SET_SUBREAPER);
    

    这样,init进程P1将不再是孤儿进程的父进程。
    下面程序演示了Linux中的sub'reaper进程。

    #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());
            }
        }
    }
    

    2 实践内容与截图

    2.1 fork()实践


    代码:

    #include <unistd.h>
    #include <stdio.h>
     
    int main ()
    {
        pid_t fpid; 
    	printf("this is %d my parent = %d\n", getpid(),getppid());
        fpid = fork();
        if (fpid < 0)
            printf("error in fork!");
        else if (fpid == 0)
        {
            printf("i am the child process, my process id is %d/n", getpid());//返回调用进程的PID
        }
        else
        {
            printf("i am the parent process, my process id is %d/n", getpid());//返回调用进程的PID
        }
        return 0;
    }
    

    2.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)行注释,子进程先运行完成:

    只取消第(2)行注释,子进程的ppiid改为其他PID号

    2.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);
        }
    }
    

    2.4 Linux中的subreaper进程

    #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());
            }
        }
    }
    

    3 总结

    本章主要讨论了UNIX/LINUX进程管理,阐述了多任务处理原则,并使用实践了用于进程管理的系统调用,包括fork、wait、exec和exit;也实践了父进程与子进程的关系,包括进程终止和父进程等待操作之间关系的详细描述。

  • 相关阅读:
    mysql正则表达式
    阿里云OSS 获取目录下所有文件
    docker 部署mvc项目 <四>
    docker部署项目 <三>
    docker 安装mysql数据库 <二>
    docker安装 <一>
    安装 Docker <一>
    Mongodb字段自增长
    EF的使用<三>
    EF 简单介绍<一>
  • 原文地址:https://www.cnblogs.com/yu15141310373/p/16773769.html
Copyright © 2020-2023  润新知