這里會記錄學習MIT6.1810的筆記:
我主要會記錄一些自己對每一節(jié)課的理解,方便日后復習。
同時也會要求自己把每個課程作業(yè)按照最高要求去完成,會記錄一些LAB里有難度的地方。
所有代碼會維護在
https://github.com/yixuaz/6.1810-2023
SCHEDULE使用的是如下:
https://pdos.csail.mit.edu/6.828/2023/schedule.html
根據(jù)里面的教程,我在WINDOWS下配置了WSL2來跑LINUX。
代碼會維護在
Lec 1
什么是操作系統(tǒng)?
硬件:CPU、RAM、硬盤、網絡等
用戶應用:如shell(sh)、編譯器(cc)、數(shù)據(jù)庫(DB)等
內核服務:如文件系統(tǒng)(FS)、進程、內存、網絡等
系統(tǒng)調用
kernel
Kernel是計算機資源的守護者。當你打開計算機時,Kernel總是第一個被啟動。Kernel程序只有一個,它維護數(shù)據(jù)來管理每一個用戶空間進程。Kernel同時還維護了大量的數(shù)據(jù)結構來幫助它管理各種各樣的硬件資源,以供用戶空間的程序使用。Kernel同時還有大量內置的服務,例如,Kernel通常會有文件系統(tǒng)實現(xiàn)類似文件名,文件內容,目錄的東西,并理解如何將文件存儲在磁盤中。所以用戶空間的程序會與Kernel中的文件系統(tǒng)交互,文件系統(tǒng)再與磁盤交互。
操作系統(tǒng)的目的是什么?
- 在多個應用之間復用硬件
- 隔離應用程序, 保證安全,彼此錯誤不會相互影響
- 允許合作的應用程序之間共享資源
- 為了可移植性而抽象硬件
- 提供有用的服務
高效vs易用。
高效性需要在low-level接近硬件進行操作,而易用性需要提供high-level的可移植接口。因此,實現(xiàn)既簡單可移植又高效的接口是個挑戰(zhàn)。
強大vs簡單。
我們也想要提供強大的操作系統(tǒng)服務來輔助應用程序運行,但又期望其擁有簡單的接口以便于程序員理解和使用。目標是提供既簡單又功能強大的接口。
靈活性vs安全性
內核具備靈活的接口。我們希望給程序員完全的自由,但是實際上又不能是真正的完全自由,因為我們不想要程序員能直接訪問到硬件,干擾到其他的應用程序,或者干擾操作系統(tǒng)的行為。
Lab 1
1. Write an uptime program that prints the uptime in terms of ticks using the uptime system call.
這個問題只要調用一下uptime這個系統(tǒng)調用即可。
int
main(int argc, char *argv[])
{
printf("ticks: %d\n", uptime());
exit(0);
}
2. Support regular expressions in name matching for find. grep.c has some primitive support for regular expressions.
學習find里支持REGEX代碼,那個正則表達式的理解,是這道題最有收獲;我會簡單講解一下;
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
char buf[1024];
int match(char*, char*);
void
grep(char *pattern, int fd)
{
int n, m;
char *p, *q;
m = 0;
while((n = read(fd, buf+m, sizeof(buf)-m-1)) > 0){
m += n;
buf[m] = '\0';
p = buf;
while((q = strchr(p, '\n')) != 0){
*q = 0;
if(match(pattern, p)){
*q = '\n';
write(1, p, q+1 - p);
}
p = q+1;
}
if(m > 0){
m -= p - buf;
memmove(buf, p, m);
}
}
}
int
main(int argc, char *argv[])
{
int fd, i;
char *pattern;
if(argc <= 1){
fprintf(2, "usage: grep pattern [file ...]\n");
exit(1);
}
pattern = argv[1];
if(argc <= 2){
grep(pattern, 0);
exit(0);
}
for(i = 2; i < argc; i++){
if((fd = open(argv[i], O_RDONLY)) < 0){
printf("grep: cannot open %s\n", argv[i]);
exit(1);
}
grep(pattern, fd);
close(fd);
}
exit(0);
}
// Regexp matcher from Kernighan & Pike,
// The Practice of Programming, Chapter 9, or
// https://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
int matchhere(char*, char*);
int matchstar(int, char*, char*);
int
match(char *re, char *text)
{
if(re[0] == '^')
return matchhere(re+1, text);
do{ // must look at empty string
if(matchhere(re, text))
return 1;
}while(*text++ != '\0');
return 0;
}
// matchhere: search for re at beginning of text
int matchhere(char *re, char *text)
{
if(re[0] == '\0')
return 1;
if(re[1] == '*')
return matchstar(re[0], re+2, text);
if(re[0] == '$' && re[1] == '\0')
return *text == '\0';
if(*text!='\0' && (re[0]=='.' || re[0]==*text))
return matchhere(re+1, text+1);
return 0;
}
// matchstar: search for c*re at beginning of text
int matchstar(int c, char *re, char *text)
{
do{ // a * matches zero or more instances
if(matchhere(re, text))
return 1;
}while(*text!='\0' && (*text++==c || c=='.'));
return 0;
}
當然可以。這段代碼主要通過兩個函數(shù) matchhere 和 matchstar 來實現(xiàn)對 . 和 * 的支持。
對于 . 的支持
在 matchhere 函數(shù)中有這樣一段代碼:
if(*text!='\0' && (re[0]=='.' || re[0]==*text))
return matchhere(re+1, text+1);
這里,re[0]=='.' 是檢查正則表達式當前字符是否為 .。如果是,或者正則表達式的當前字符與文本的當前字符匹配,那么函數(shù)將繼續(xù)比較正則表達式的下一個字符和文本的下一個字符。
對于 * 的支持
* 的匹配比較特殊。在正則表達式中,* 意味著匹配前面的字符零次或多次。例如,a* 可以匹配 "", "a", "aa", "aaa" 等。
在 matchhere 函數(shù)中有這樣一段代碼:
if(re[1] == '*')
return matchstar(re[0], re+2, text);
如果 re[1] 是 *,則調用 matchstar 函數(shù)。
在matchstar 函數(shù)中:
do{
if(matchhere(re, text))
return 1;
}while(*text!='\0' && (*text++==c || c=='.'));
這里有一個循環(huán),嘗試著匹配 * 前面的字符零次、一次、兩次等,直到不能匹配為止。對于每一種嘗試,都調用 matchhere 函數(shù)來檢查剩余的正則表達式是否與剩余的文本匹配。
注意條件 (*text++==c || c=='.'),這里的 c 是 * 前面的字符。這意味著,只有當文本的當前字符與 c 匹配或 c 是 . 時,循環(huán)才會繼續(xù)。
3. modify the shell to not print a $ when processing shell commands from a file
這道題實際上就是要我們實現(xiàn)一個isatty函數(shù);
isatty 是一個在 POSIX-like 系統(tǒng)中常見的函數(shù),它的功能是判斷給定的文件描述符是否與一個終端關聯(lián)。
這個函數(shù)返回一個布爾值,如果 fd(文件描述符)與終端關聯(lián),則返回 1,否則返回 0。
int
isatty (int fd)
{
struct stat st;
if (fstat(fd, &st) < 0) {
printf("Error calling fstat\n");
exit(1);
}
// 1 == CONSOLE
return st.type == T_DEVICE && st.dev == 1;
}
int
getcmd(char *buf, int nbuf)
{
// 0 is stdin
if (isatty(0)) {
write(2, "$ ", 2);
}
...
}
測試方法:
之前那些$都消失了

4. modify the shell to support wait
在 shell 腳本中,wait 是一個內置命令,用于使腳本暫停執(zhí)行并等待一個或多個后臺進程完成執(zhí)行。
作用:
同步化:當在腳本中啟動一個或多個后臺進程時,你可能希望在繼續(xù)執(zhí)行其他命令之前等待這些后臺進程完成。這樣,你可以確保某些任務在其他任務開始之前已經完成。
資源管理:在某些情況下,你可能不希望在之前的后臺進程完成之前啟動新的進程。這可以避免過多的并發(fā)進程占用太多資源。
因為這個命令直接和shell相關,需要讓shell進程wait后臺子進程執(zhí)行完成,所以和cd是一個level;
具體實現(xiàn)方法是,對于BACKGROUND的命令,F(xiàn)ORK之后,子進程去執(zhí)行實際命令,父進程進行wait;(這里的父進程,其實是shell fork出來的子進程)
然后在shell 進程沒使用wait 命令時,我們可以判斷,這個cmd如果是個background的,我們就啥也不做,別的就wait下

然后在shell 的main函數(shù)里,加上
else if(!strcmp(buf, "wait\n")){
// wait must be called by the parent, not the child.
int status, pid;
while((pid = wait(&status)) != -1)
printf("%d done, status:%d\n", pid, status);
continue;
}
...
struct cmd *curcmd = parsecmd(buf);
if(fork1() == 0)
runcmd(curcmd);
else if (curcmd->type != BACK)
wait(0);
}
測試方法:

5. modify the shell to support lists of commands, separated by ";"
本身就支持了;
6. modify the shell to support sub-shells by implementing "(" and ")"
本身就支持了;

7.modify the shell to support tab completion
這個題雖然標了easy, 但真的要實現(xiàn)到和linux shell 一樣的效果,還是要寫非常多的代碼,和非常大的思考量;而且還需要稍微改下kernel;
要實現(xiàn)這個,xv6的代碼有這么幾個問題
- 我經過研究,發(fā)現(xiàn)在讀入命令時,是kernel的
console.c基于行,也就是說除非你打了回車,不然SHELL段是讀不到數(shù)據(jù)的。所以需要讓\t也要觸發(fā)緩沖區(qū)刷新。讓shell測可以讀到數(shù)據(jù) - shell測拿到數(shù)據(jù)后,判斷如果最后是
\t;那么就可以根據(jù)是否有空格,來判斷是命令補全,還是文件補全;并且判斷是否有多個選項;有多個選項,找到最長公共前綴進行補全;單個匹配,就直接補全;沒有匹配,就沒有任何效果; - 補全的時候發(fā)現(xiàn),直接
write進stdin 是不work的;但是如果往stdout寫,這個字符是不可修改的。所以這邊也需要修改kernel;具體做法是當往stdout寫時,console.c接受到寫數(shù)據(jù),判斷第一個是否為\t, 如果是的話,需要維護kernel buf的3個指針,同時調用consputc去模擬用戶的輸入;
所以要實現(xiàn)這個功能,還是比較復雜;我們來看下3個模塊的代碼時怎么改動的;
在console.c中:

在console.c的consoleintr函數(shù)中:

在shell里添加支持的代碼:
char* knowncommands[] = {
"ls","cat","echo","history","cd","wait","sleep","pingpong","primes","find","xargs","uptime",
"wc","zombie","grind","usertests","stressfs","sh","rm","mkdir","ln","kill","init","grep","forktest"
};
char* common_longest_prefix(const char* a, const char* b) {
int len = 0;
while (a[len] && a[len] == b[len]) len++;
char* prefix = (char*) malloc(strlen(a));
strcpy(prefix, a);
prefix[len] = '\0';
return prefix;
}
int
completecmd(char *buf)
{
if (!strlen(buf)) return 0;
int matchcount = 0;
char *match = NULL;
char *fileidx = strrchr(buf, ' ');
// If the buffer contains a space, look for filenames
if (fileidx) {
struct dirent dir;
int fd = open(".", O_RDONLY);
fileidx += 1;
if (fd >= 0) {
while (read(fd, &dir, sizeof(dir)) == sizeof(dir)) {
if (dir.inum == 0) continue;
if (!strncmp(dir.name, fileidx, strlen(fileidx))) {
if (matchcount) {
if (matchcount == 1) printf("\n%s", match);
printf("\n%s", dir.name);
matchcount++;
char* newmatch = common_longest_prefix(match, dir.name);
free(match);
match = newmatch;
} else {
char *copy = malloc(strlen(dir.name));
strcpy(copy, dir.name);
match = copy;
matchcount = 1;
}
}
}
close(fd);
}
}
else { // Otherwise, look for command matches
for (int i = 0; i < sizeof(knowncommands) / sizeof(knowncommands[0]); i++) {
if (!strncmp(knowncommands[i], buf, strlen(buf))) {
if (matchcount) {
printf("\n%s", knowncommands[i]);
matchcount++;
} else {
match = knowncommands[i];
matchcount = 1;
}
}
}
}
char* result;
if (matchcount == 1) {
// If a single match is found, complete the command
result = malloc(strlen(fileidx) + strlen(match) + 3);
strcpy(result, "\t");
strcpy(result + 1, buf);
strcpy(result + 1 + strlen(buf), "\t");
if(fileidx)
strcpy(result + 2 + strlen(buf), match + strlen(fileidx));
else
strcpy(result + 2 + strlen(buf), match + strlen(buf));
} else if (matchcount > 1) {
printf("\n$ ");
int buflen = strlen(buf);
result = malloc(buflen + strlen(match) + 3);
strcpy(result, "\t\t");
strcpy(result + 2, buf);
strcpy(result + 2 + buflen, match + strlen(fileidx));
} else {
result = malloc(strlen(buf) + 2);
strcpy(result, "\t");
strcpy(result + 1, buf);
}
write(1, result, strlen(result));
free(result);
return strlen(buf);
}
int
getcmd(char *buf, int nbuf)
{
if (isatty(0)) {
write(2, "$ ", 2);
}
memset(buf, 0, nbuf);
int idx = 0, cc = 0;
char c;
for(; idx+1 < nbuf; ) {
cc = read(0, &c, 1);
if(cc < 1)
break;
if(c == '\t') { // Tab key
buf[idx] = 0;
idx -= completecmd(buf);
}
...
}
測試方法
- 打l, 按tab, 看是否可以提示
- 補全的字符,是否可以刪除
- 最長公共字符串是否可以補全
-
文件名是否可以補全
1697899484120.png
8. modify the shell to keep a history of passed shell commands
如何保存history? 設計一個循環(huán)數(shù)組
如何支持上下的按鍵 跳回到上一條commands?
這個也要改動一下kernel,因為上,下鍵是由3個字符表示;所以我們需要在console.c,維護一個狀態(tài)機;并且還要改動,不能每次只傳一個字符過來
enum {
NORMAL,
ESCAPE_SEEN,
BRACKET_SEEN
} input_state = NORMAL;
...
//
// the console input interrupt handler.
// uartintr() calls this for input character.
// do erase/kill processing, append to cons.buf,
// wake up consoleread() if a whole line has arrived.
//
void normalintr(int c)
{
switch(c){
case C('P'): // Print process list.
procdump();
break;
case C('U'): // Kill line.
while(cons.e != cons.w &&
cons.buf[(cons.e-1) % INPUT_BUF_SIZE] != '\n'){
cons.e--;
consputc(BACKSPACE);
}
break;
case C('H'): // Backspace
case '\x7f': // Delete key
if(cons.e != cons.w){
cons.e--;
consputc(BACKSPACE);
}
break;
case '\t': // Tab key
cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;
cons.w = cons.e;
wakeup(&cons.r);
break;
default:
if(c != 0 && cons.e-cons.r < INPUT_BUF_SIZE){
c = (c == '\r') ? '\n' : c;
// echo back to the user.
consputc(c);
// store for consumption by consoleread().
cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;
if(c == '\n' || c == C('D') || cons.e-cons.r == INPUT_BUF_SIZE){
// wake up consoleread() if a whole line (or end-of-file)
// has arrived.
cons.w = cons.e;
wakeup(&cons.r);
}
}
break;
}
}
void dirintr(int c)
{
switch(c){
case 'A':
case 'B':
while(cons.e != cons.w){
cons.e--;
consputc(BACKSPACE);
}
cons.buf[cons.e++ % INPUT_BUF_SIZE] = '\x1b';
cons.buf[cons.e++ % INPUT_BUF_SIZE] = '[';
cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;
cons.w = cons.e;
wakeup(&cons.r);
}
}
void
consoleintr(int (*getc)(void))
{
int c;
acquire(&cons.lock);
while((c = getc()) >= 0){
switch(input_state) {
case NORMAL:
if (c == '\x1b') {
input_state = ESCAPE_SEEN;
} else {
normalintr(c);
}
break;
case ESCAPE_SEEN:
if (c == '[') {
input_state = BRACKET_SEEN;
} else {
input_state = NORMAL;
}
break;
case BRACKET_SEEN:
// Reset to the normal state for any other character.
input_state = NORMAL;
dirintr(c);
break;
}
}
release(&cons.lock);
}
下面就是shell 的改動,
#define HISTSIZE 21
char history[HISTSIZE][100];
int historySt = 0;
int historyEnd = 0;
int historyPos = 0;
...
void write_shell_stdin(char* buf) {
char *result = malloc(strlen(buf) + 3);
strcpy(result, "\t\t");
strcpy(result + 2, buf);
write(1, result, strlen(result));
}
int
getcmd(char *buf, int nbuf)
{
if (isatty(0)) {
write(2, "$ ", 2);
}
memset(buf, 0, nbuf);
int idx = 0, cc = 0;
char c;
for(; idx+1 < nbuf; ) {
cc = read(0, &c, 1);
if(cc < 1)
break;
if(c == '\t') { // Tab key
buf[idx] = 0;
idx -= completecmd(buf);
}
else if(c == '\x1b') {
char d = 0, e = 0;
read(0, &d, 1);
read(0, &e, 1);
if (d == '[' && e == 'A') {
if (historyPos > historySt) historyPos--;
write_shell_stdin(history[historyPos]);
} else if (d == '[' && e == 'B') {
if (historyPos < historyEnd) historyPos++;
write_shell_stdin(history[historyPos]);
}
}
else {
buf[idx++] = c;
if (c == '\n' || c == '\r') break;
}
}
buf[idx] = '\0';
if(buf[0] == 0) // EOF
return -1;
return 0;
}
void
add_history(char *cmd) {
int size = historyEnd - historySt;
if (size < 0) size += HISTSIZE;
strcpy(history[historyEnd], cmd);
// exchange \n to 0
history[historyEnd][strlen(cmd) - 1] = 0;
historyEnd++;
historyPos = historyEnd;
if(historyEnd == HISTSIZE) historyEnd = 0;
if (size == HISTSIZE - 1) {
historySt++;
if(historySt == HISTSIZE) historySt = 0;
}
}
int
main(void)
{
static char buf[100];
int fd;
// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
if(fd >= 3){
close(fd);
break;
}
}
// Read and run input commands.
while(getcmd(buf, sizeof(buf)) >= 0){
if (strlen(buf) > 1) add_history(buf);
if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
// Chdir must be called by the parent, not the child.
buf[strlen(buf)-1] = 0; // chop \n
if(chdir(buf+3) < 0)
fprintf(2, "cannot cd %s\n", buf+3);
continue;
}
else if(!strcmp(buf, "wait\n")){
// wait must be called by the parent, not the child.
int status, pid;
while((pid = wait(&status)) != -1)
printf("%d done, status:%d\n", pid, status);
continue;
}
else if(!strcmp(buf, "history\n")){
for(int i = historySt, j = 1; i != historyEnd; j++)
{
printf("%d %s\n", j, history[i]);
i++;
if (i == HISTSIZE) i = 0;
}
continue;
}
struct cmd *curcmd = parsecmd(buf);
if(fork1() == 0)
runcmd(curcmd);
else if (curcmd->type != BACK)
wait(0);
}
// this line to help xarg not timeout after supporting isatty
write(2, "$ \n", 3);
exit(0);
}
最后kernel/uart.c,把函數(shù)傳進去,這樣是為了循環(huán)放在內部,可以讀到多個字符,判斷狀態(tài)機。

