操作系统_学习日志_进程内容补充以及shell实验

版权声明:我的码云地址:https://gitee.com/ZzYangHao https://blog.csdn.net/qq_21049875/article/details/81432609

  又玩了快两期,一直在观望的小米笔记本第二代更新的有些失望,觉得可以考虑入手其他的本子了….好了开始学习orz
  之前的第二篇内容很少,也就写了一些进程间通讯的方式以及一些了解,为了方便记录实验与以及补充关于信号的使用及知识,所以重新写了篇。

  
  

异常

  异常分为四种:
这里写图片描述
  还有一种是中断,原因是来自IO设备的信号,属于异步,返回行为是总是返回到下一条指令,比如输入ctrl+c,网络中一个包接收完毕,都会触发这样的中断。
  陷阱:有意的异常,主要用途实在用于程序与内核之间提供一个像过程一样的接口,也就是系统调用,系统调用运行在内核模式中,执行特权指令,访问的是内核中的栈。
  
  

进程

  异常是允许操作系统内核提供进程概念的基本构造块。
  进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中,上下文是由程序运行所需的状态组成,而状态包括存放在程序中的代码和数据以及堆栈、通用目的寄存器的内容、程序寄存器、环境变量以及打开文件描述符的集合。
  如下图的结构:
这里写图片描述
  一个逻辑流的执行时间与另一个流重叠,成为并发流,多个流并发地执行的一般现象称为并发
  进程的状态可以分为三种:
    1、运行
    2、停止
    3、终止
  
  关于进程相关的一些接口,就不说了,在第二篇中有相关的内容。

  、
  

信号

  操作系统利用异常来支持进程上下文切换,信号,是一种更高层的软件形式的异常,它允许进程和内核中断其他进程,它通知进程系统中发生了一个某种类型的事件来达到中断。
  常用的信号如下:
这里写图片描述
  
  在c语言中,发送信号可以使用:

int kill(pid_t pid,int sig);
//pid:目的进程号,sig:信号,成功返回0,否则-1
unsigned int alarm(unsigned int secs);
//返回前一次闹钟剩余的秒数,若以前没设置则为0
//alarm函数会在secs秒后发送SIGALRM信号给调用进程,如果secs为0,则不会调度
//安排新的闹钟

  接收信号:
  所有的上下文切换都是通过调用某个异常处理器(exception handler)完成的,当进程p从内核模式切换到用户模式时(如系统调用返回或者是完成了一次上下文切换),内核会检查进程p的未被阻塞的待处理信号的集合 pnb 值:pnb = pending & ~blocked (pending待处理信号,blocked是设置阻塞的集合)
    如果 pnb == 0,那么就把控制交给进程 p 的逻辑流中的下一条指令
    如果 pnb != 0:
      选择 pnb 中最小的非零位 k,并强制进程 p 接收信号 k
      接收到信号之后,进程 p 会执行对应的动作
      对 pnb 中所有的非零位进行这个操作
      最后把控制交给进程 p 的逻辑流中的下一条指令
  每个信号类型都有一个预定义的『默认动作』,可能是以下的情况:
    终止进程
    终止进程并 dump core
    停止进程,收到 SIGCONT 信号之后重启
    忽略信号

typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler)
//成功则返回指向前次处理程序的指针,否则返回SIG_ERR

  如果handler是SIG_IGN,那么忽略类型为num的信号
  如果是SIG_DFL,那么类型为signum的信号会恢复默认行为。
  否则,handler就是用户自定义的函数,这个函数被称为信号处理程序 ,当信号处理程序被调用,则这一行为称为,捕获信号
  控制流如下图:
这里写图片描述
  
  阻塞信号:
  显式阻塞,就需要使用 sigprocmask 函数了,以及其他一些辅助函数(要注意的一点就是,在多进程中,子进程会继承父进程的阻塞集合):

int sigprocmask(int how,const sigset *set,sigset_t *oldset);
int sigemptyset(sigset *set)
int sigfillset(sigset *set)
int sigaddset (sigset *set,int signum)
int sigdelset (sigset *set,int signum)

  其他的函数就不用怎么介绍了,主要说说sigprocmask,其行为取决于how的值,当how为:
    SIG_BLOCK:把set中的信号添加到blocked(阻塞的信号集合)中,相当于blocked=blocked | set
    SIG_UNBLOCK:从blocked删除set中的信号。
    SIG_SETMASK:block=set
安全处理信号
  信号处理器的设计并不简单,因为它们和主程序并行且共享相同的全局数据结构,尤其要注意因为并行访问可能导致的数据损坏的问题,这里提供一些基本的指南(后面的课程会详细介绍)
  规则 1:信号处理器越简单越好
  例如:设置一个全局的标记,并返回
  规则 2:信号处理器中只调用异步且信号安全(async-signal-safe)的函数
  诸如 printf, sprintf, malloc 和 exit 都是不安全的!
  规则 3:在进入和退出的时候保存和恢复 errno
  这样信号处理器就不会覆盖原有的 errno 值
  规则 4:临时阻塞所有的信号以保证对于共享数据结构的访问
  防止可能出现的数据损坏
  规则 5:用 volatile 关键字声明全局变量
  这样编译器就不会把它们保存在寄存器中,保证一致性
  规则 6:用 volatile sig_atomic_t 来声明全局标识符(flag)
  这样可以防止出现访问异常
  这里提到的异步信号安全(async-signal-safety)指的是如下两类函数:
    所有的变量都保存在栈帧中的函数
    不会被信号中断的函数
  Posix 标准指定了 117 个异步信号安全(async-signal-safe)的函数(可以通过 man 7 signal 查看)
  
  
正确的信号处理
  信号并不是排队处理的,因为在pending位向量中,每种类型的信号只对应一位,所以每种类型最多只能有一个未处理的信号。
  比如下面这段代码,发送5次同一个信号给主线程,那么会调用5次么?并不,在我的虚拟机上跑的结果是只跑了一次,而其余的信号都被扔了。

int val=0;

void func(){
    val++;
    sleep(1);
    return;
}
int main(){
    int i;
    signal(SIGUSR2,func);
    if(fork()==0){
        for(i=0;i<5;i++){
            kill(getppid(),SIGUSR2);
        }
        exit(0);
    }
    wait(NULL);
    printf("val: %d",val);
    return 0;
}

  
  
避免并发错误
  如下代码,在向jobs列表中增加与删除工作的时候,看上去很安全,但是有一种可能的危险,当fork后,内核调度的是子进程运行,然后在父进程运行之前,子进程就结束了,此时就传SIGCHILD信号过去,也就是在父进程阻塞之前执行了信号处理函数,导致错误,当然以下程序我运行了好几遍,很小几率出现错误,但是实际工作肯定是不能容许这样的错误发生。

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<apue.h>
#define SIZE 100
int jobs[SIZE]={0};
int currindex=0;
typedef __sigset_t sigset_t;

void addjob(pid_t tmp){
    jobs[currindex]=tmp;
    currindex++;
}
void deljob(pid_t tmp){
    int i;
    for(i=0;i<SIZE;i++){
        if(jobs[i]==tmp){
            jobs[i]=0;
            return;
        }
    }
    fprintf(stdout,"error! \n");
    exit(0);
}
void func(){
    pid_t pid;
    sigset_t mask,pre;
    sigfillset(&mask);
    while((pid=waitpid(-1,NULL,0))>0){
        sigprocmask(0,&mask,&pre);
        deljob(pid);
        sigprocmask(2,&pre,0);
        fprintf(stdout,"del pid:%d \n",pid);
    }
}
int main(){
    pid_t pid;
    signal(SIGCHLD,func);
    sigset_t mask,pre;
    sigfillset(&mask);
    int i=0;
    while(i<SIZE){
        pid=fork();
        if(pid==0){
           execve("/root/Desktop/lerning/hello",NULL,NULL);
        }
        sigprocmask(0,&mask,&pre);
        addjob(pid);
        sigprocmask(2,&pre,0);
        fprintf(stdout,"add pid:%d \n",pid);
        i++;
    }
    exit(0);
}

  既然可能在父进程之前结束发送信号,那么我们就在父进程的时候阻塞SIGCHILD,且因为子进程继承父进程的文件信息包括阻塞信号的集合,所以我们需要在子进程中恢复blocked即可。这样当子进程先结束,发送SIGCHILD信号的时候会被阻塞,然后执行完addjob才能恢复blocked,然后执行信号处理,然后就ok了~_~

int main(){
    pid_t pid;
    signal(SIGCHLD,func);
    sigset_t mask,pre,one_mask,one_pre;
    sigfillset(&mask);
    sigemptyset(&one_mask);
    int i=0;
    while(i<SIZE){ 
        sigprocmask(0,&one_mask,&one_pre);
        pid=fork();
        if(pid==0){
           sigprocmask(2,&one_pre,0);
           execve("/root/Desktop/lerning/hello",NULL,NULL);

        }
        sigprocmask(0,&mask,&pre);
        addjob(pid);
        sigprocmask(2,&pre,0);
        fprintf(stdout,"add pid:%d \n",pid);
        i++;
    }
    exit(0);
}

  
  

等待信号

  当我们在程序中,需要在主进程中等待子进程运行结束,然后去做一些事,如:

volatile sig_atomic_t pid;
void handler(){
    int olderrno=errno;
    pid=wait(NULL);
    errno=olderrno; 
}

int main(){
    sigset_t mask,pre;
    signal(SIGCHILD,handler);
    sigemptyset(&mask);
    sigaddset(&mask,SIGCHILD);
    while(1){
        //阻塞SIG_CHILD,防止子进程结束过早,在初始化pid=0之前就发送SIG_CHILD信号
        sigprocmask(SIG_BLOCK,&mask,&pre);
        if(Fork()==0){
            sigprocmask(SIG_SETMASK,&pre,NULL);
            dosomething1();
            exit(0);
        }
        pid=0;
        sigprocmask(SIG_SETMASK,&pre,NULL);
        while(!pid);
        dosomething2();
    }
    return 0;
}

  按照上面所写的代码,为了等待子进程结束再执行主线程的任务,使用一个死循环去等待,这样会很耗费内存。
  我们可以使用sleep让进程睡眠改成如下,但是这会让程序运行太慢。

while(!pid)
sleep(1);

  而使用pause似乎没什么问题,但可能会出现一种情况,当SIG_CHILD信号在while之后pause之前发出,那么主进程将会一直挂起或者睡眠。

while(!pid)
pause();

  为了解决这样的问题,可以使用sigsuspend:

int sigsuspend(const sigset_t *mask);
//mask为暂时替换当前阻塞的集合,之后会还原为之前的集合
//该函数相当于在调用pause()函数前设置sigpromask函数进行阻塞,之后恢复blocked集合,如下:
//相当于的下面代码原子(不可中断)的版本。
//sigpromask(SIG_BLOCK,&mask,&pre)
//pause();
//sigpromask(SIG_SETMASK,&pre,NULL);

  使用上面的函数,修改为如下,为了避免在进入while之后立马接受到SIG_CHILD,就干脆直接把阻塞范围扩大到包括while,然后使用sigsuspend将阻塞集合替换为没有SIG_CHILD的集合,这样就确保了能在while当即使用了pause,又能在进程在pause后的状态下接受到SIG_CHILD信号,然后唤醒主线程。

int main(){
    sigset_t mask,pre;
    signal(SIGCHILD,handler);
    sigemptyset(&mask);
    sigaddset(&mask,SIGCHILD);
    while(1){
        sigprocmask(SIG_BLOCK,&mask,&pre);
        if(Fork()==0){
            sigprocmask(SIG_SETMASK,&pre,NULL);
            dosomething1();
            exit(0);
        }
        pid=0;
        while(!pid)
        sigsuspend(&pre);
        sigprocmask(SIG_SETMASK,&pre,NULL);
        dosomething2();

    }
    return 0;
}

  
  
  

实验:制作一个简单的shell

  实验文件去csapp官网下,我做的是第二版的,在刚开始解压实验的时候,发现很多文件,有一些小慌,因为前阵子都是分析汇编代码,很久没怎么写代码了,所以这次实验和以前不一样,难度也是很大,需要冷静下来,一步一步分析shell的一些实现,以及lab中的文件。
  以下是实验文件:
这里写图片描述
  其中tracexx.txt为测试文件,sdriver.pl为测试程序,tshref是已经实现的shell ,tshref.out则是程序输出结果的保存文件,我截取输出结果的一段内容分析吧:

./sdriver.pl -t trace03.txt -s ./tshref -a "-p"
#
# trace03.txt - Run a foreground job.
#
tsh> quit

  可以看到测试的方法为./sdriver.pl -t trace03.txt -s ./tshref -a "-p",而以下的内容就是trace03.txt的内容,它会让的tshref这个shell中执行quit命令。
  而我们要做的就是编写tsh.c,制作一个我们自己的shell文件,最好输出结果能和tshref的输出结果一样,所以我们可以参考测试文件trace中命令行对应的结果,去编写我们的shell。
  下面我们来看看tsh.c的代码,分析我们要做什么,下面是main函数的编写,是一个编写多进程程序很好的例子,tsh中的代码可以对应《深入理解计算机系统》中P524的内容,也可以参考上面的去完成我们的实验,书上面缺少的是对后台进程使用完的回收,最好去看书了解,下面来看看main吧:

int main(int argc, char **argv) 
{
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1; /* emit prompt (default) */

    /* Redirect stderr to stdout (so that driver will get all output
     * on the pipe connected to stdout) */
    dup2(1, 2);

    /* Parse the command line */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
        case 'h':             /* print help message */
            usage();
        break;
        case 'v':             /* emit additional diagnostic info */
            verbose = 1;
        break;
        case 'p':             /* don't print a prompt */
            emit_prompt = 0;  /* handy for automatic testing */
        break;
    default:
            usage();
    }
    }

    /* Install the signal handlers */

    /* These are the ones you will need to implement */
    signal(SIGINT,  sigint_handler);   /* ctrl-c */
    signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */
    signal(SIGCHLD, sigchld_handler);  /* Terminated or stopped child */

    /* This one provides a clean way to kill the shell */
    signal(SIGQUIT, sigquit_handler); 

    /* Initialize the job list */
    initjobs(jobs);

    /* Execute the shell's read/eval loop */
    while (1) {

    /* Read command line */
    if (emit_prompt) {
        printf("%s", prompt);
        fflush(stdout);
    }
    if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
        app_error("fgets error");
    if (feof(stdin)) { /* End of file (ctrl-d) */
        fflush(stdout);
        exit(0);
    }

    /* Evaluate the command line */
    eval(cmdline);
    fflush(stdout);
    fflush(stdout);
    } 

    exit(0); /* control never reaches here */
}

  可以看到main函数编写得让人看着舒服,注释应该说的很,可以直接从signal那开始看起。
  程序在运行while就开始相当于运行shell了,开始一个死循环,然后调用eval函数去处理命令行,而我们去看tsh.c文件,发现几个函数都是空着的,这就意味着,这次实验目的就是补充这空缺的函数。

/* Here are the functions that you will implement */
void eval(char *cmdline);//执行命令行
int builtin_cmd(char **argv); //解析命令行第一个参数是否是shell内置命令
void do_bgfg(char **argv); //执行bg与fg内置命令,一般为前台任务
void waitfg(pid_t pid); //等待前台任务完成

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

  下面是tsh.c中的一些宏定义,与jobs的结构体。

/* Misc manifest constants */
#define MAXLINE    1024   /* max line size */
#define MAXARGS     128   /* max args on a command line */
#define MAXJOBS      16   /* max jobs at any point in time */
#define MAXJID    1<<16   /* max job ID */

/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1    /* running in foreground */
#define BG 2    /* running in background */
#define ST 3    /* stopped */

/* 
 * Jobs states: FG (foreground), BG (background), ST (stopped)
 * Job state transitions and enabling actions:
 *     FG -> ST  : ctrl-z
 *     ST -> FG  : fg command
 *     ST -> BG  : bg command
 *     BG -> FG  : fg command
 * At most 1 job can be in the FG state.
 */

/* Global variables */
extern char **environ;      /* defined in libc */
char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */
int verbose = 0;            /* if true, print additional output */
int nextjid = 1;            /* next job ID to allocate */
char sbuf[MAXLINE];         /* for composing sprintf messages */

struct job_t {              /* The job struct */
    pid_t pid;              /* job PID */
    int jid;                /* job ID [1, 2, ...] */
    int state;              /* UNDEF, BG, FG, or ST */
    char cmdline[MAXLINE];  /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */

  是不是看着很头大?因为上面代码包括很多内容,但是都有注释了,要耐心看下去,这些参数的作用。
  那么,既然要一个shell,我们就要知道shell是怎么用的,如果熟悉Linux,没什么问题。
  简单来说,shell 有两种执行模式:
     如果用户输入的命令是内置命令,那么 shell 会直接在当前进程执行(例如 jobs)
     如果用户输入的是一个可执行程序的路径,那么 shell 会 fork 出一个新进程,并且在这个子进程中执行该程序(例如 /bin/ls -l -d)
  在这里,我们只要简单的实现shell的几个内置命令,bg,fg,jobs,quit。
   job control:允许用户更改进程的前台/后台状态以及京城的状态(running, stopped, or terminated)
  ctrl-c :会触发 SIGINT 信号并发送给每个前台进程,默认的动作是终止该进程
  ctrl-z :会触发 SIGTSTP 信号并发送给每个前台进程,默认的动作是挂起该进程,直到再收到 SIGCONT 信号才继续
  jobs :命令会列出正在执行和被挂起的后台任务
  bg job :命令可以让一个被挂起的后台任务继续执行
  fg job :命令可以让一个被挂起的前台任务继续执行
  
  

eval()函数实现:
  eval的任务很简单,如下流程图:
这里写图片描述
  代码:

void eval(char *cmdline) 
{
    char *argv[MAXLINE];    /*argument list of execve()*/
    char buf[MAXLINE];      /*hold modified commend line*/
    int bg;                 /*should the job run in bg or fg?*/
    pid_t pid;
    sigset_t mask;          /*mask for signal*/

    stpcpy(buf,cmdline);
    bg = parseline(buf,argv);//parseline用于判断命令行最后一个参数是不是&
    //是的话后台执行,不是则前台执行,以及整理参数格式

    if(argv[0]==NULL){
        return;     /*ignore empty line*/
    }

    if(!builtin_cmd(argv)){                         /*not a build in cmd*/
        sigemptyset(&mask);
        sigaddset(&mask,SIGCHLD);
        sigprocmask(SIG_BLOCK,&mask,NULL);           /*block the SIGCHLD signal*/

        if((pid = fork())==0)
        {
            sigprocmask(SIG_UNBLOCK,&mask,NULL);     /*unblock the SIGCHLD signal in child*/
            setpgid(0,0);                            /*puts the child in a new process group*/

            if(execve(argv[0],argv,environ)<0){
                printf("%s: Command not found\n",argv[0]);
                exit(0);
            }
        }

        addjob(jobs, pid, bg?BG:FG,cmdline);        /*add job into jobs*/
        sigprocmask(SIG_UNBLOCK,&mask,NULL);        /*unblock the SIGCHLD signal in parent*/

        if(bg){
            printf("[%d] (%d) %s", pid2jid(pid), pid,cmdline);
        }
        else{
            waitfg(pid); //等待前台任务执行结束
        }
    }
    return;

}

  这上面要注意的就是,之前说提到的并发问题,控制一个任务addjob与deljob的执行顺序,是在子进程产生一个job,然后放置job执行过快,在主进程addjob还没发生,子进程就结束发送信号调用deljob,所以用信号阻塞。
  还有一个注意的地方就是,要把fork出的进程设置为新的进程组,否则在主进程中发生的中断或者收到的信号将被子进程收到。
  
builtin_cmd函数实现
  这里就简单了,单纯的判断内置命令。
  代码如下:

int builtin_cmd(char **argv) 
{
     /* not a builtin command */
    if(!strcmp(argv[0],"quit"))
        exit(0);
    if(!strcmp(argv[0],"&"))
        return 1;
    if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg")){
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0],"jobs")){
        listjobs(jobs);
        return 1;
    }

    return 0;     /* not a builtin command */
}

  
  
do_bgfg函数实现
   这里要注意的是,要知道bg、fg在shell中是如何使用的,以及使用的格式,从而判断参数格式是否正确,以及判断是fg或者bg决定是否后台运行。
   代码:

void do_bgfg(char **argv) 
{
     pid_t pid;
    struct job_t *job;
    char *id = argv[1];

    if(id==NULL){       /*bg or fg has the argument?*/
        printf("%s command requires PID or %%jobid argument\n",argv[0]);
        return;
    }

    if(id[0]=='%'){     /*the argument is a job id*/
        int jid = atoi(&id[1]);
        job = getjobjid(jobs,jid);
        if(job==NULL){
            printf("%%%d: No such job\n",jid);
            return;
        }
    }else if(isdigit(id[0])){               /*the argument is a pid is a digit number?*/
        pid = atoi(id);
        job = getjobpid(jobs,pid);
        if(job==NULL){
            printf("(%d): No such process\n",pid);
            return ;
        }
    }else{
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    kill(-(job->pid),SIGCONT); /*send the SIGCONT to the pid*/

    if(!strcmp(argv[0],"bg")){ /*set job state ,do it in bg or fg*/
        job->state = BG;
        printf("[%d] (%d) %s", job->jid, job->pid,job->cmdline);
    }else{
        job->state = FG;
        waitfg(job->pid);
    }
    return;
}

  
  
waitfg函数实现
   这里使用sleep(0)可以让cpu去调度其他进程,并非是真的要进程挂起0毫秒,这里也可以使用sigsuspend函数然后再该函数中的while前后阻塞SIG_CHILD信号,但是这样就要额外定义一个全局变量pid_t,然后在SIG_CHILD信号处理函数中pid_t=waitpid(),如果pid!=pid_t就继续waitfg中的while循环,等待前台执行完成才跳出循环。

void waitfg(pid_t pid)
{
     while(pid == fgpid(jobs)){
        sleep(0);
    }
    return;

    return;
}

  
  
sigchld_handler函数实现
  按照测试文件输出编写的函数,输出进程状态,输出进程是正常暂停或者终止,还是因为收到信号而暂停,WNOHANG|WUNTRACED参数,该参数的作用是判断当前进程中是否存在已经停止或者终止的进程,如果存在则返回pid,不存在则立即返回。
   代码:

void sigchld_handler(int sig) 
{
    pid_t pid;
    int status;
    while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED))>0){
        if(WIFEXITED(status)){  /*process is exited in normal way*/
            deletejob(jobs,pid);
        }
        else if(WIFSIGNALED(status)){/*process is terminated by a signal*/
            printf("Job [%d] (%d) terminated by signal %d\n",pid2jid(pid),pid,WTERMSIG(status));
            deletejob(jobs,pid);
        }
        else if(WIFSTOPPED(status)){/*process is stop because of a signal*/
            printf("Job [%d] (%d) stopped by signal %d\n",pid2jid(pid),pid,WSTOPSIG(status));
            struct job_t *job = getjobpid(jobs,pid);
            if(job !=NULL )job->state = ST;
        }
    }
    if(errno != ECHILD)
        unix_error("waitpid error");

    return;
}

  
  
sigint_handler函数实现
  因为我们为每个子进程设置为新的进程组,所以要想ctrl+c中断任务,就需要kill发送信号
   代码:

void sigint_handler(int sig) 
{
    pid_t pid = fgpid(jobs);
    if(pid != 0){
        kill(-pid,SIGINT);
        /*let the sigchld_handler to delete the job in jobs?*/
    }
    return;
}

  
  
sigtstp_handler函数实现
  也很简单实现。
   代码:

void sigtstp_handler(int sig) 
{
    pid_t pid = fgpid(jobs);

    if(pid!=0 ){
        struct job_t *job = getjobpid(jobs,pid);
        if(job->state == ST){  /*already stop the job ,do‘t do it again*/
            return;
        }else{
            kill(-pid,SIGTSTP);
        }
    }
    return;

}

  至此,该实验完成,其实这个实验让我们补充的部分虽然不多,但是却非常核心,包含了我前面写的解决并发问题使用信号,以及sleep(0)或者sigsuspend同步或者说等待信号,以及信号相关的函数,以及多进程的使用的知识点,而这也是初步了解进程的必要基础。
   上面的代码并非完全是我写的,我也参考了其他的解析,然后看书理解写的,其实过程很简单,只是需要了解的知识相对较多罢了。

猜你喜欢

转载自blog.csdn.net/qq_21049875/article/details/81432609