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
這個估計是最好寫的,判斷一下命令類型就好了。quit和jobs都很好寫,fg和bg的具體操作被封裝在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
這個是最后寫的,畢竟bg和fg我也是上個月才會用的.....
大概就是利用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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 牛客网C++面经 容器和算法
- 下一篇: Mac 重置 idea