OS_learning
chapter1:操作系统接口
操作系统的工作是:
- 将计算机资源在多个程序之间共享,并给程序提供一系列比硬件本身更有用的服务
- 管理并抽象底层硬件。(如word不用关心自己使用的是哪种硬盘)
- 多路复用硬件,让多个程序“看起来”是同时运行的。
- 给程序间提供一种受控的交互方式,使得程序之间可以共享数据、共同工作
操作系统通过接口向用户程序提供服务。接口设计依赖于少量的机制,通过这些机制的组合提供强大、通用的功能。
- kernel(内核):为运行的程序提供服务的一种特殊程序。每个运行着的程序叫做进程,每个进程的内存中存储指令、数据和堆栈。一个计算机可以拥有多个进程,但是只能有一个内核。
- 每当进程需要调用内核时,它会触发一个system call(系统调用),system call进入内核执行相应的服务然后返回。
- shell 是一个普通的程序,它接受用户输入的命令并且执行它们。不是内核的一部分。
- xv6内核提供了Unix传统系统调用的一部分。
系统调用 | 描述 |
---|---|
fork() | 创建进程 |
exit() | 结束当前进程;0代表以正常状态退出,1代表以非正常状态退出 |
wait() | 等待子进程结束;等待子进程退出,返回子进程PID;没有子程序则返回-1 |
kill(pid) | 结束 pid 所指进程 |
getpid() | 获得当前进程 pid |
sleep(n) | 睡眠 n 秒 |
exec(filename, *argv) | 加载并执行一个文件 |
sbrk(n) | 为进程内存空间增加 n 字节 |
open(filename, flags) | 打开文件,flags 指定读/写模式 |
read(fd, buf, n) | 从文件中读 n 个字节到 buf |
write(fd, buf, n) | 从 buf 中写 n 个字节到文件 |
close(fd) | 关闭打开的 fd |
dup(fd) | 复制 fd |
pipe( p) | 创建管道, 并把读和写的 fd 返回到p |
chdir(dirname) | 改变当前目录 |
mkdir(dirname) | 创建新的目录 |
mknod(name, major, minor) | 创建设备文件 |
fstat(fd) | 返回文件信息 |
link(f1, f2) | 给 f1 创建一个新名字(f2) |
unlink(filename) | 删除文件 |
进程和内存
- xv6进程由两部分组成:用户内存空间(指令,数据,堆栈)+ 仅对内核可见的进程状态
- 分时特性:在可用CPU之间不断切换。当一个进程不在执行时,xv6保存它的CPU寄存器,当他们再次被执行时恢复这些寄存器的值。
- 内核将每个进程和一个PID(process identifier)关联起来
- 一个进程可以通过调用fork()来创建新进程;创建生成的是子进程,其内容同创建它的进程(父进程)一样。
fork
函数在父进程、子进程中都返回(一次调用两次返回)。对于父进程它返回子进程的 pid,对于子进程它返回 0。
1 |
|
I/O和文件描述符
- 文件描述符是一个整数,它代表了一个进程可以读写的被内核管理的对象。
- 获取方式:打开文件、目录、设备或创建一个管道pipe或复制已经存在的文件描述符。
管道
管道是一个小的内核缓冲区,它以文件描述符对的形式提供给进程,一个用于写操作,一个用于读操作。从管道的一端写的数据可以从管道的另一端读取。管道提供了一种进程间交互的方式。
- 管道会进行自我清扫
- 管道可以传输任意长度的数据
- 管道允许同步
文件系统
xv6 文件系统提供文件和目录,文件就是一个简单的字节数组,而目录包含指向文件和其他目录的引用。
系统调用
read接受三个参数
- 第一个参数:文件描述符,指向一个之前打开的文件。
- shell会确保默认情况下,当一个程序启动时,文件描述符0连接到console的输入,文件描述符1连接到console的输出。
- 第二个参数是指向某段内存的指针,程序可以通过指针对应的地址读取内存中的数据。
- 第三个参数是代码想读取的最大长度。
Lab0 实验搭建
- 使用wsl2。(在debian12中进行)
- 安装编译器(突然意识到,之前计组实验好像安装过了,所以直接使用)
- 安装QEMU
- 下载xv6并编译
遇到的问题:在 make qemu
时出现了死循环错误。解决办法:修改makefile,将 CFLAGS := -Wall -O -fno-omit-frame-pointer -ggdb -DSOL_UTIL -MD -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie
注释掉。
其余的直接安装官方步骤进行就可以了。
Lab1
sleep的实现
- 在user文件夹下新建sleep.c文件
- 研究一下其他函数文件是怎么实现的
- 如何传递命令行参数
- 头文件需要引用哪一些
例子:echo.c
1 |
|
直接使用同款头文件就行了
- #include “user/user.h”这里涵盖了sleep()等系统调用函数
- #include “kernel/types.h” 是一些数据类型
- 获取参数:int main(int argc, char *argv[])
- argc是参数个数
- argv是输入的字符串
- 将字符串转为int–atoi函数
- 写完sleep.c之后,要将它添加到makefile文件,一同编译出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18UPROGS=\
$U/_cat\
$U/_echo\
$U/_forktest\
$U/_grep\
$U/_init\
$U/_kill\
$U/_ln\
$U/_ls\
$U/_mkdir\
$U/_rm\
$U/_sh\
$U/_stressfs\
$U/_usertests\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\ - 然后再编译
make qemu
,成功后,调用sleep函数就可以了
pingpong的实现
- 理解管道(pipe)
- 管道是一种半双工的通信机制,允许两个进程进行通信。管道有两个文件描述符:
- p[0]:用于读取数据
- p[1]:用于写入数据
- 管道是一种半双工的通信机制,允许两个进程进行通信。管道有两个文件描述符:
- 使用pipe()创建管道;此处需要创建两个管道,一个用于父级向子级发送一个字节;一个用于父级从子级读取字节
- read(fd, buf, n):从文件中读 n 个字节到 buf
1 |
|
primes
=>素数筛法
素数筛法是一种经典的算法,用于找出一定范围内的所有素数。它的基本思想是:
- 从 2 开始,将当前数标记为素数。
- 将该数的所有倍数标记为非素数。
- 移动到下一个未被标记的数,重复上述过程。
在并发版本中,每个素数会由一个独立的进程处理,并通过管道将筛选后的数字传递给下一个进程。
=>并发版本的思路
在并发版本中,每个素数对应一个进程,进程之间通过管道通信:
- 第一个进程:生成数字 2 到 35,并将它们写入管道。
- 后续进程:
- 从管道中读取第一个数字,这个数字就是当前进程负责的素数。
- 打印这个素数。
- 继续读取后续的数字,筛选掉当前素数的倍数,将剩余的数字写入下一个管道。
- 创建一个新的子进程来处理下一个素数。
- 终止条件:
- 当没有更多数字需要处理时,进程退出。
=>需要两个管道,一个用于筛选得到新的一组数据,另一个用于接收新一组的数据,进一步进行筛选;
直到达到限制
=>文件描述符的管理:
每个进程需要关闭不需要的文件描述符,以避免资源耗尽。
父进程在创建子进程后,需要关闭子进程使用的管道端。
=>进程的递归创建:
每个素数对应一个进程,进程之间通过管道连接,形成一条链。
=>终止条件:
当管道中没有更多数字时,进程退出。
主进程需要等待所有子进程退出。
1 |
|
find的实现
阅读ls.c文件,了解如何获取目录信息
- dirent:读取目录项
1
2
3
4
5struct dirent {
ushort inum; // i-node 号,指向文件系统中的 i-node
char name[DIRSIZ]; // 文件/目录名(不含路径)
}; - stat:获取文件属性
1
2
3
4
5
6
7
8struct stat {
int dev; // 设备号
uint ino; // i-node 号
short type; // 文件类型 (T_DIR=目录, T_FILE=普通文件)
short nlink; // 硬链接数
uint size; // 文件大小
}; dirent
结构本身不会告诉你文件是目录还是普通文件 ,要用stat()
查询。
- dirent:读取目录项
读取目录:
- 用read()读取目录项
1
2
3
4
5
6int fd = open(".",O_RDONLY);
struct dirent de;
while(read(fd,&de,sizeof(de))==sizeof(de)){
printf("File:%s\n",de.name);
}
close(fd);
- 获取文件属性
- 用read()读取目录项
1 |
|
xargs的实现
1 |
|