zoukankan      html  css  js  c++  java
  • CSAPP实验6 : shlab

    linking的部分看了一半跳过去了....据说你南的ICS讲linking非常不错,于是我就心安理得了
    exception的部分还在看,估计这周末可以看完(吧),先留坑

    我来填坑辣

    终于赶在五一(假期前)写完了第六个lab,书也推完了第八章,四舍五入就是五月前搞定了前八章内容,还是很不戳的。整个四月都非常忙,各种ddl和期中满天飞。五月也不空闲,还有EL、听说读写ddl、马原、银川、大雾期中、军理。希望人没事.jpg

    这次lab的一个明显特点就是答案都可以在书上找到,各种详细的例子书上都有,因此写起来只需要多翻书就好了。不过由于这次lab的测试比较弱,而我对自己的代码水平又没有什么信心,因此下面贴的代码看看就好,莫当真

    说了不少废话,讲正题吧

    前置姿势

    这一章主要从硬件软件(系统)两个层面讲了异常控制流(exceptional control flow)的各种姿势,这次的lab主要关注的是软件方面的东西。软件的控制流交移是通过信号(signal)的发送和接受来进行的,这一点因为写过QT所以不是特别虚。看前面的内容如果不过瘾还可以搭配OS的教材一起看,正好就顺手入了SJTU的书,希望暑假能啃一啃。

    本章引入的鲁棒性的问题非常重要,也就是多线程过程中的竞争问题。这种问题常常发生在不同线程对统一数据的修改和读取中。为了解决这个问题本章引入了原子性的概念,大意就是某些操作不可分割、控制流不能从中间被移接。事实上还有的概念,大意就是在修改、读取数据的时候阻塞其余信号,也就是给数据“上锁”

    本次lab的测试非常之水,毕竟系统方面的测试用用例还是比较难调出问题(我对形式化验证这方面也很感兴趣)。

    代码

    eval

    这一部分可以抄书。具体需要搞清楚fork()会创建一个新的进程,而execve()则相当于用新的进程"覆盖"当前进程。因此如果想要实现shell的调用需要先fork()再在子进程里execve()

    根据writeup的提示需要给子进程分配组id,在eval中新建进程需要调用addjob()来修改进程表中的内容,因此需要阻塞其它信号以免出现问题。

    void eval(char *cmdline) 
    {
        int olderrno = errno;
        char *argv[MAXARGS];
        int bg = parseline(cmdline, argv);
    
        if (argv[0] == NULL) return ;
        if ( builtin_cmd(argv) ) return ;
    
        pid_t pid;
        // Child Process
        sigset_t mask, prev;
        sigemptyset(&mask);
        sigaddset(&mask, SIGCHLD);
    
        sigprocmask(SIG_BLOCK, &mask, &prev);
        pid = fork();
        if (pid < 0)
            unix_error("Fork error");
        if ( pid == 0) {
            sigprocmask(SIG_BLOCK, &prev, NULL);
            setpgid(0, 0);
            if ( execve(argv[0], argv, environ) < 0) {
                printf("%s: Command not found
    ", argv[0]);
                exit(0);
            }
        }
        if (!bg) {
            addjob(jobs, pid, FG, cmdline);
            sigprocmask(SIG_SETMASK, &prev, NULL);
            waitfg(pid);
        } else {
            addjob(jobs, pid, BG, cmdline);
            struct job_t *job = getjobpid(jobs, pid);
            printf("[%d] (%d) %s", job->jid, job->pid, cmdline);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        errno = olderrno;
        return;
    }
    

    builtin_cmd

    这个估计是最好写的,判断一下命令类型就好了。quitjobs都很好写,fgbg的具体操作被封装在do_bgfg()里面了,在这里直接用就好了

    int builtin_cmd(char **argv) 
    {
        char *builtin_args[4] = {"quit", "bg", "fg", "jobs"};
        for (int i = 0; i < 4; ++ i) {
            if ( strcmp(builtin_args[i], argv[0]) ) continue;
    
            switch (argv[0][0]) {
                case 'q': exit(0);
                case 'f': {
                    do_bgfg(argv);
                    break;
                }
                case 'b': {
                    do_bgfg(argv);
                    break;
                }
                case 'j': listjobs(jobs);
            }
            return 1;
        }
        return 0;     /* not a builtin command */
    }
    
    

    waitfg

    这一段也很好写。这是用来等待前台进程结束的,那么就用一个死循环来不停地监听pid进程是否在前台

    由于在监听的时候访问了全局数据结构,因此也要阻塞信号

    void waitfg(pid_t pid)
    {
        sigset_t mask, prev;
        sigfillset(&mask);
        sigprocmask(SIG_BLOCK, &mask, &prev);
        struct job_t *job = getjobpid(jobs, pid);
        sigprocmask(SIG_SETMASK, &prev, NULL);
        for (; job != NULL && job->state == FG; ) {
            sigfillset(&mask);
            sigprocmask(SIG_BLOCK, &mask, &prev);
            job = getjobpid(jobs, pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
        }
        return;
    }
    

    do_bgfg

    这个是最后写的,毕竟bgfg我也是上个月才会用的.....

    大概就是利用kill发送信号,同时修改job对应的state。在发送信号的时候也要阻塞其余信号因为访问了全局数据结构

    这里还要判一下输入是否合法,这个比较麻烦不过对着trace慢慢搞就好了

    void do_bgfg(char **argv) 
    {
        char *id_str = argv[1];
    
        //fg bg error handling
        if (id_str == NULL) {
            printf("%s command requires PID or %%jobid argument
    ", argv[0]);
            return ;
        }
    
        int flag = (id_str[0] == '%'), jid, pid;
        id_str += flag;
    
        for (int i = 0, _len = strlen(id_str); i < _len; ++ i) {
            if (!isdigit(id_str[i])) {
                printf("%s: argument must be a PID or %%jobid
    ", argv[0]);
                return ;
            }
        }
    
        sigset_t mask, prev;
        sigfillset(&mask);
        sigprocmask(SIG_BLOCK, &mask, &prev);
    
        if (!flag) {
            sscanf(id_str, "%d", &pid);
            jid = pid2jid(jid);
        } else {
            sscanf(id_str, "%d", &jid);
        }
    
        struct job_t *job = getjobjid(jobs, jid);
    
        if (job == NULL) {
            if (flag) printf("%%%d: No such job
    ", jid);
            else printf("(%d): No such process
    ", pid);
            sigprocmask(SIG_SETMASK, &prev, NULL);
            return ;
        }
    
        if (argv[0][0] == 'b') {
            printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
            job->state = BG;
            kill(-(job->pid), SIGCONT);
        } else if (argv[0][0] == 'f') {
            job->state = FG;
            kill(-(job->pid), SIGCONT);
            sigprocmask(SIG_SETMASK, &prev, NULL);
            waitfg(job->pid);
        }
        sigprocmask(SIG_SETMASK, &prev, NULL);
        return;
    }
    

    hanlders

    三个放在一起说

    sigint_handler()最好写,只需要利用fgpid()搭配kill()就好了

    注意要阻塞信号

    void sigint_handler(int sig) 
    {
        int olderrno = errno;
    
        sigset_t mask, prev;
        sigfillset(&mask);
        sigprocmask(SIG_BLOCK, &mask, &prev);
        pid_t pid = fgpid(jobs);
        if (pid == 0) return ;
        struct job_t *job = getjobpid(jobs, pid);
        if (kill(-pid, sig) < 0)
            unix_error("Sigint error");
        sigprocmask(SIG_SETMASK, &prev, NULL);
        
        errno = olderrno;
        return;
    }
    
    

    sigstp_handler()和上面是一样的,就不说了

    需要注意的是,在这两个handler中我们没必要更改数据结构,而是可以等sigchld_handler()一起改,这样写起来可以清晰一些

    sigchld_handler()有几个坑点

    1. 要注意waitpid()默认行为是挂起父进程直至子进程终止(terminate),写到这里的时候记得往回翻书,或者看writeup的提示也行
    2. 好像就没了....

    回收的时候需要对子进程的死因讨论一下,同时记得阻塞就好了

    在读这一章的时候深刻体会到了术语明确的重要性,比如说停止(stop)和终止(terminate)和中断(interrupt)这三个,放在中文里感觉就没有区别啊

    void sigchld_handler(int sig) 
    {
        int olderrno = errno;
    
        int status;
        pid_t pid = waitpid(-1, &status, WNOHANG | WUNTRACED);
        if (!pid) return ;
    
        sigset_t mask, prev;
        sigfillset(&mask);
        sigprocmask(SIG_BLOCK, &mask, &prev);
    
        struct job_t *job = getjobpid(jobs, pid);
    
        if (WIFSTOPPED(status)) {
            job->state = ST;
            printf("Job [%d] (%d) stopped by signal %d
    ", job->jid, job->pid, WSTOPSIG(status) );
        } else {
            if (!WIFEXITED(status))
                printf("Job [%d] (%d) terminated by signal %d
    ", job->jid, job->pid, WTERMSIG(status) );
            deletejob(jobs, job->pid);
        }
        sigprocmask(SIG_SETMASK, &prev, NULL);
    
        errno = olderrno;
        return;
    }
    
    

    于是就写完辣!

    最后的最后我还写了一个小程序来快速检验答案是否正确,大概长这样

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    char str1[200];
    
    int main(int argc, char const *argv[])
    {
    	system("make clean; make");
    	for (int i = 1; i <= 16; ++ i) {
    		sprintf(str1, "make test%02d > myp%02d.out", i, i);
    		system(str1);
    		sprintf(str1, "make rtest%02d > std%02d.out", i, i);
    		system(str1);
    		printf("id: %d completed
    ", i);
    	}
    	for (int i = 1; i <= 16; ++ i) {
    		sprintf(str1, "wc -l std%02d.out", i);
    		system(str1);
    		sprintf(str1, "wc -l myp%02d.out", i);
    		system(str1);
    	}
    	return 0;
    }
    

    当然这个只是初步的,具体正确性还要自行比对一下....不过这个看起来也是可以自动化的,我太懒了就鸽了吧,毕竟要考大雾了淦

    本文来自博客园,作者:jjppp。本博客所有文章除特别声明外,均采用CC BY-SA 4.0 协议

  • 相关阅读:
    64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多
    selenium3 + python3
    selenium3 + python
    selenium3 + python
    selenium3+python-多窗口、句柄(handle)
    selenium3 + python
    selenium3 + python
    selenium3 + python 操作浏览器基本方法
    Appium
    Appium
  • 原文地址:https://www.cnblogs.com/jjppp/p/14686584.html
Copyright © 2011-2022 走看看