开坑
课程:MIT 6.S081
lab:xv6
目标:掌握os,尽量手搓一个demo
Chapter 0
Note
shell维护三个文件描述符,1,0,2
进程维护文件描述符表
read系统调用会前进文件的偏移量,返回0作为文件结尾
2 >& 1:将标准错误流重定向到标准输出流
管道是一对读取和写入的文件描述符,shell使用fork来产生子进程,新的cmd来运行从管道的读描述符来获取数据
文件描述符偏移量相同的两种情况:dup调用和fork出子进程
close释放文件描述符
exec来重定向文件描述符,一个进程执行新进程时,替换掉的是进程的内存映像,但是维护的文件描述符表不会发生改变
若子进程不在退出前关闭文件描述符,父进程就会在子进程偏移的地方继续写,所以子进程的文件偏移后的文件描述符的位置会保留给父进程
当进程试图访问设备文件时,内核会将对于设备文件的读写操作转发给相应的设备驱动程序或者内核代码,而不是用文件系统来操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int p[2]; char *argv[2]; argv[0] = "wc"; argv[1] = 0; pipe(p); if(fork() == 0) { close(0); dup(p[0]); close(p[0]); close(p[1]); exec("/bin/wc", argv); } else { write(p[1], "hello world\n", 12); close(p[0]); close(p[1]); }
|
notice:重定向是进程级别而不是系统级别
p[0]是读端口,读端口绑定到标准输入(通过dup(p[0])),其他父子进程从标准输入读取数据时实际会从读端口读取
Lab1:
父子进程间的管道通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
int main(int argc,char *argv[]){ if (argc > 1) { printf("meaningless parameter\n"); exit(1); } int pid; int p1[2]; int p2[2]; char byte; pipe(p1); pipe(p2); pid = fork(); if (pid > 0) { close(p1[0]); close(p2[1]); if(write(p1[1],"1",1) != 1){ printf("error parent send\n"); exit(1); } if (read(p2[0], &byte, 1) == 1) { printf("%d:recieve pong\n", getpid()); } else { printf("error parent recieve\n"); close(p1[0]); close(p2[1]); exit(1); } close(p1[0]); close(p2[1]); } else if (pid == 0) { close(p1[1]); close(p2[0]); if(read(p1[0],&byte,1) == 1){ printf("%d:recieved ping\n", getpid()); if(write(p2[1], "1", 1) != 1){ printf("error child send\n"); exit(1); } exit(0); } else { printf("error child recieve\n"); exit(1); } } else { printf("fork error"); exit(1); } }
|
notice:父子进程不同步,无法保证父进程发送数据时,子进程已经准备好接收数据,出现条件竞争,所以要使用同步机制来保证
管道实现素数筛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h"
#define INDEX_READ 0 #define INDEX_WRITE 1 #define MAX 35
void child_read(int pipelines_ptc[]){ int pid; int pipelines_ctgc[2]; int num; pipe(pipelines_ctgc); close(pipelines_ptc[INDEX_WRITE]); if (read(pipelines_ptc[INDEX_READ], &num, sizeof(num)) > 0) { printf("prime %d\n", num); } else{ close(pipelines_ctgc[INDEX_READ]); close(pipelines_ctgc[INDEX_WRITE]); close(pipelines_ptc[INDEX_READ]); close(pipelines_ptc[INDEX_WRITE]); exit(0); } if ((pid = fork()) > 0) { printf("child process\n"); close(pipelines_ctgc[INDEX_READ]); printf("close success!\n"); int each_num; while (read(pipelines_ptc[INDEX_READ], &each_num, sizeof(each_num)) > 0) { printf("Read success!\n"); if (each_num % num != 0) { write(pipelines_ctgc[INDEX_WRITE], &each_num, sizeof(each_num)); printf("write success!\n"); } } close(pipelines_ctgc[INDEX_WRITE]); close(pipelines_ptc[INDEX_READ]); wait(0); } else{ child_read(pipelines_ctgc); } }
int main(int argc, char *argv[]){ int pipelines_ptc[2]; int pid; pipe(pipelines_ptc); if ((pid = fork()) > 0) { close(pipelines_ptc[INDEX_READ]); for (int i = 2; i < MAX; i++) { write(pipelines_ptc[INDEX_WRITE], &i, sizeof(i)); wait(0); } } else{ child_read(pipelines_ptc); } exit(0); }
|
find 命令实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/fs.h"
char* fmtname(char *path) { static char buf[DIRSIZ+1]; char *p; for(p=path+strlen(path); p >= path && *p != '/'; p--) ; p++; if(strlen(p) >= DIRSIZ) return p; memmove(buf, p, strlen(p)); memset(buf+strlen(p), ' ', DIRSIZ-strlen(p)); return buf; }
void find(char *path, char *name){ char buf[512]; char *p; int fd; struct dirent de; struct stat st;
if((fd = open(path, 0)) < 0){ fprintf(2, "find: cannot open %s\n", path); return; }
if(fstat(fd, &st) < 0){ fprintf(2, "ls: cannot stat %s\n", path); close(fd); return; }
switch(st.type){ case T_FILE: if (strcmp(fmtname(path), name) == 0) { printf("%s", path); return; } break; case T_DIR: if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){ printf("find: path too long\n"); break; } strcpy(buf, path); p = buf+strlen(buf); *p++ = '/'; while (read(fd, &de, sizeof(de)) == sizeof(de)) { if (de.inum == 0 || !strcmp(de.name, ".") || !strcmp(de.name, "..")) continue;
memmove(p, de.name, DIRSIZ); p[DIRSIZ] = 0; if (!strcmp(de.name, name)) { printf("%s\n", buf); continue; }
if(stat(buf, &st) < 0){ printf("find: cannot stat %s\n", buf); continue; }
switch (st.type) { case T_DIR: find(buf, name); break; default: break; } } close(fd); return; } close(fd); return; }
int main(int argc, char *argv[]){ if (argc < 3) { printf("Usage: 'find dir filename'\n"); exit(1); } for (int i = 2; i < argc; i++) { find(argv[1], argv[i]); } exit(0); }
|
xargs 命令实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include "kernel/types.h" #include "kernel/stat.h" #include "user/user.h" #include "kernel/fs.h" #include "kernel/param.h"
#define STDIN 0
int main(int argc,char *argv[]) { char buf[1024]; char c; char *Argv[MAXARG]; int index=0; int i;
for(i=1;i<=argc-1;i++) { Argv[i-1]=argv[i]; } while(1) { index=0; memset(buf,0,sizeof(buf)); char *p=buf; i=argc-1; while(1) { int num=read(STDIN,&c,1); if(num!=1) exit(0); if(c==' '||c=='\n') { buf[index++]='\0'; Argv[i++]=p; p=&buf[index]; if(c=='\n') break; } else { buf[index++]=c; } } Argv[i]=0; int pid = fork(); if(pid==0) { exec(Argv[0],Argv); } else { wait(0); } } exit(0); }
|
无缓冲通道CSP线程
CSP并发编程是一种进程间的通信方式,不需要两个进程共享状态,而是通过消息传递的方式来进行通信,可以解决死锁和条件竞争的问题,其中使用管道(pipeline)的无缓冲I/O是一种常见的来实现同步的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main
import ( "fmt" "time" )
func worker(ch chan bool) { fmt.Println("Worker: Waiting for task...") <-ch fmt.Println("Worker: Performing task...") time.Sleep(2 * time.Second) fmt.Println("Worker: Task completed!") }
func main() { ch := make(chan bool)
go worker(ch)
time.Sleep(1 * time.Second)
fmt.Println("Main: Sending task...") ch <- true fmt.Println("Main: Task sent!")
time.Sleep(3 * time.Second) }
|
Chapter 1
Note
将操作系统作为库调用会带来一系列问题
操作系统将磁盘管理抽象成文件系统,并且提供了对文件系统的接口(系统调用),来实现对应用程序访问存储的隔离
隔离的必要性:物理资源组织,异常处理和安全性
隔离方案:multiplexing,CPU分时复用
fork等操作系统接口抽象出可以和CPU交互的进程,进程在和应用程序交互,所以应用程序才能复用CPU
为什么虚拟地址能保证应用程序不直接访问物理地址的安全性?
- 虚拟地址提供了对物理地址的抽象和扩展,便于操作系统对进程进行隔离
- 虚拟地址可以设置一些针对物理地址的权限,并且内存管理单元MMU(用于根据页表将虚拟地址映射为虚拟地址)也可以对访问进行拦截和保护保证
exec系统调用是unix接口,将应用程序的内存镜像放到进程内存里