首页 > 技术文章 > 20191317王鹏宇第三章学习笔记

wpy-1049363419 2021-10-24 19:55 原文

第三章:Unix/Linux进程管理

知识点归纳总结

本章讨论了 Unix/Linux中的进程管理;阐述了多任务处理原则;介绍了进程概念;并以一个编程示例来说明多任务处理、上下文切换和进程处理的各种原则和方法。

多任务处理系统支持动态进程创建、进程终止,以及通过休眠与唤醒实现进程同步、进程关系,以及以二叉树的形式实现进程家族树,从而允许父进程等待子进程终止;提供了一个具体示例来阐释进程管理函数在操作系统内核中是如何工作的;然后,解释了 Unix/Linux中各进程的来源,包括系统启动期间的初始进程、INIT进程、守护进程、登录进程以及可供用户执行命令的sh进程;

对进程的执行模式进行了讲解,以及如何通过中断、异常和系统调用从用户模式转换到内核模式;再接着,描述了用于进程管理的Unix/Linux系统调用,包括 fork, wait、exec和exit ;

阐明了父进程与子进程之间的关系,包括进程终止和父进程等待操作之间关系的详细描述;解释了如何通过IN1T进程处理孤儿进程,包括当前Linux中的subreaper进程,并通过示例演示了 subreaper进程;接着,详细介绍了如何通过exec更改进程执行映像,包括exeeve系统调用、命令行参数和环境变量;

解释了 I/O重定向和管道的 原则及方法,并通过示例展示了管道编程的方法;


其中让我最有收获的几个部分如下:

  • 多任务处理系统
  • 进程同步
  • 进程终止
  • MT系统中的进程管理
  • Unix/Linux中的进程
  • 进程管理的系统调用
  • I/O重定向
  • 管道

文件流与文件描述符:

sh进程有三个用于终端I/O的文件流:stdin (标准输入),stdout(标准输出)和stderr(标准错误)。每个流都是指向执行映像堆区中FILE结构体的一个指针,如下文所示。

FILE *stdin-----------> FILE structure
          ----------------------------
          char fbuf[SIZE]
          int counter, index, etc.
          int fd = 0;	// fd[0] in PROC <= from KEYBOARD
          ----------------------------

FILE *stdout ----------> FILE structure
          -----------------------------
          char fbuf[SIZE]
          int counter, index, etc.
          int fd = 1;	// fd[l] in PROC ==> to SCREEN
          -----------------------------

FILE *stderr ----------> FILE structure
          -----------------------------
          char fbuf[SIZE]
          int counter, index, etc.
          int fd = 2;	// fd[2] in PROC «■> to SCREEN also
          -----------------------------

每个文件流对应Linux内核中的一个打开文件。每个打开文件都用一个文件描述符(数字)表示。stdin、stdout、stderr的文件描述符分别为0、1、2。当某个进程复刻出一个子进程时,该子进程会继承父进程的所有打开文件。因此,子进程也具有与父进程相同的文件流和文件描述符



进程管理中的系统调用:

fork():fork系统调用用于创建一个新进程,称为子进程,它与进行fork()调用的进程(父进程)并发运行。创建新的子进程后,两个进程都将执行fork()系统调用之后的下一条指令。子进程使用相同的PC(程序计数器),相同的CPU寄存器,相同的打开文件,这些文件在父进程中使用。
举例:

#include <stdio.h> 
#include <sys/types.h> 
#include <unistd.h> 
int main()
{
    // 在此指令后生成两个运行相同程序的进程
    fork();
    fork();
    fork();
    printf("Hello world!\n"); 
    return 0; 
}

hello打印的次数等于创建的进程数。进程总数=2^n, 其中n是fork系统调用的数量。 所以这里n=3,2^3=8。

wait():父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

exec():在用fork函数创建子进程后,子进程往往要调用exec函数以执行另一个程序。当子进程调用exec函数时,会将一个二进制可执行程序的全路径名作为参数传给exec,exec会用新程序代换子进程原来全部进程空间的内容,而新程序则从其main函数开始执行,这样子进程要完成的任务就变成了新程序要完成的任务了。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。进程还是那个进程,但实质内容已经完全改变。

exit():直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是”清理I/O缓冲”。



实践内容:教材实例编程
代码:

type.h文件:

/**************type.h file*********/
#include <stdio.h>
#define NPROC    9
#define SSIZE 1024
//PROC status
#define FREE 0
#define READY 1
#define SLEEP 2
#define ZOMBIE 3
typedef struct proc
{
    struct proc *next;
    int *ksp;
    int pid;
    int ppid;
    int status;
    int priority;
    int kstack[SSIZE];
}PROC;

queue.c文件:
//#include "type.h"

/************queue file*********/

int enqueue(PROC **queue, PROC *p)
{
    PROC *q = *queue;
    if (q == 0|| p->priority > q->priority)
    {
        *queue = p;
        p->next =q;
    }
    else
    {
        while (q->next && p->priority <= q->next->priority) 
        {
            q = q->next;
        }
        p->next = q->next;
        q->next = p;
    }
}

PROC *dequeue(PROC **queue)
{
    PROC *p = *queue;
    if (p)
    {
        *queue = (*queue)->next;
    }
    return p;
}

int printList(char *name, PROC *p)
{
    printf("%s = ", name);
    while (p)
    {
        printf("[%d %d]->",p->pid, p->priority);
        p = p->next;
    }
    printf("NULL\n");
}

ts.s文件:
/*******************ts.s************/
        .globl running, scheduler, tswitch
tswitch:
SAVE:	pushl	%eax	
		pushl	%ebx	
		pushl	%ecx	
		pushl	%edx	
		pushl	%ebp	
		pushl	%esi	
		pushl	%edi	
		pushfl	
		movl	running,%ebx	# ebx -> PROC
		movl	%esp, 4(%ebx)	# PORC.save_sp = esp
FIND:	call	scheduler	
RESUME:	movl	running,%ebx	# ebx -> PROC
		movl	4(%ebx),%esp	# esp = PROC.saved_sp
		popfl
		popl	%edi	
		pop1	%esi	
		popl	%ebp	
		popl	%edx	
		popl	%ecx	
		popl	%ebx	
		pop1	%eax	
		ret		
# stack	contents = |retPC|eax	|ebx|ecx|edx|ebp|esi|edi|eflag|
#		-1	-2	-3 -4 -5 -6 -7 -8	-9

t.c文件:
#include "type.h"
PROC proc[NPROC]; 
PROC *freeList;
PROC *readyQueue; 
PROC *running;
#include "queue.c"
int kfork()
{
    int i;
    PROC *p = dequeue(&freeList); 
    if (!p)
    {
        printf("no more proc\n");
        return(-1);
    }
    p->status = READY;
    p->priority = 1;	// ALL PROCs priority=l,except PO
    p->ppid = running->pid;
    for ( i = 1; i < 10; i++)
    {
        p->kstack[SSIZE - i] = 0; 
    }   
    p->kstack[SSIZE-1] = (int)body;
    p->ksp = &(p->kstack[SSIZE - 9]);
    enqueue(&readyQueue, p); 
    return p->pid;
}
int kexit()
{
    running->status = FREE; 
    running->priority = 0; 
    enqueue(&freeList, running);
    printList("freeList", freeList); 
    tswitch();
}
int do_kfork()
{
    int child = kfork();
    if (child < 0)
        printf("kfork failed\n");
    else
    {
        printf("proc %d kforked a child = %d\n", running->pid, child); 
        printList("readyQueue", readyQueue);
    }
    return child;
}

int do_switch()
{
    tswitch();
}
int do_exit()
{
    kexit();
}
    
int body()	// process body function
{
    int c;
    printf("proc %d starts from body()\n", running->pid); 
    while(1)
    { 
        printf("***************************************\n");
        printf("proc %d running: parent=%d\n", running->pid,running->ppid);
        printf("enter a key [f|s|q] :");
        c = getchar();
        getchar();
        switch(c)
        {
            case 'f': do_kfork ();      break;
            case 's': do_switch();      break;
            case 'q': do_exit();        break;
        }
    }
}
// initialize the MT system
int init()
{
    int i;
    PROC *p;
    for (i=0; i<NPROC; i++)
    {
        p = &proc[i];
        p->pid = i;
        p->status = FREE; 
        p->priority = 0; 
        p->next = p+1;
    }
    proc[NPROC-1].next = 0;
    freeList = &proc[0]; 
    readyQueue = 0; 
    // create PO as the initial
    p = running = dequeue(&freeList); // use proc[0] p->status = READY;
    p->ppid = 0;	// PO is its own parent
    printList("freeList", freeList);
    printf("init complete: P0 running\n");
}

//running process
int main()
{
    printf("Welcome to the MT Multitasking System\n"); 
    init();	//
    kfork();	//
    while(1)
    {
        printf("PO:switch process\n");
        if (readyQueue)
        tswitch();
    }
}

/*********** scheduler *************/
int scheduler()
{
    printf("proc %d in scheduler()\n", running->pid); 
    if (running->status == READY)
        enqueue(&readyQueue, running);
    printList("readyQueue", readyQueue);
    running = dequeue(&readyQueue);
    printf("next running = %d\n", running->pid);
}

实践截图:

这里的报错信息显示,无法识别(int)body中的body,我猜测这里是下面的body()函数,但是教材实例中是就是直接一个body,经过询问老师,加入int body()函数声明后,问题成功解决。popl则是因为我把popl错打成了pop1,犯了经典编程错误。


实践内容:教材实例编程C3.2.c
代码:

#include <stdio.h>
int main()
{
    int pid=fork();	// fork a child
    if (pid)
    {
        printf("PARENT %d CHILD=%d\n", getpid(), pid);
        printf("PARENT %d EXIT\n", getpid());
    }	// PARENT
     //sleep(1);	
    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());
    }
}
实践截图: 在sleep()注释的情况下:

在sleep()删除注释的情况下:

实践内容:教材实例编程C3.3.c
代码:

/************** C3.3.CS wait() and exit() ***************/
#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);
    }
}


实践截图:

实践内容:教材实例编程C3.4.c
代码:

//************** C3.4.C: Subreaper Process*****//
#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());
        }
    }
}
实践截图:

推荐阅读