日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

CSAPP实验6 : shlab

發布時間:2023/12/13 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 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()有幾個坑點

要注意waitpid()默認行為是掛起父進程直至子進程終止(terminate),寫到這里的時候記得往回翻書,或者看writeup的提示也行
好像就沒了....

回收的時候需要對子進程的死因討論一下,同時記得阻塞就好了

在讀這一章的時候深刻體會到了術語明確的重要性,比如說停止(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 協議

總結

以上是生活随笔為你收集整理的CSAPP实验6 : shlab的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。