第三章学习总结

第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;也实践了父进程与子进程的关系,包括进程终止和父进程等待操作之间关系的详细描述。

posted @ 2022-10-09 21:26  20201307梁辰鱼  阅读(22)  评论(0编辑  收藏  举报