MIT·6.S081 操作系统学习笔记(一):概述
课程地址:https://www.bilibili.com/video/BV19k4y1C7kA
一、操作系统的目标
- 对硬件抽象
- 让许多应用程序复用硬件(可以同时运行多个软件,且不相互干涉)
- 程序间的隔离
- 程序间的共享 Sharing
- 安全/权限系统 security
- Performance
- Range of OS,能够支持许多不同的功能
二、操作系统概述
用户空间User:运行软件的空间,例如vim,gcc,shell
Kernel:一个单独的特殊程序,管理计算机硬件资源,电脑启动时Kernel就启动
Kernel中提供了一些内置服务,例如
- 文件系统(文件名等)访问磁盘
- 对进程的管理
- 内存分配
每个正在运行的程序都称作进程,其占用一定的内存
内核也提供对进程的某个操作的权限机制,访问控制
课程主要研究内核以及内核提供的接口
系统调用:内核提供的API,跳转到内核来完成某些操作
课程实验环境:QEMU模拟器,RISC-V微处理器指令集,xv6系统
Shell指令的本质:
例如mkdir
指令,实际上它是存在于这台计算机上的一个可执行文件,一个程序,输入mkdir
指令时会运行这个程序,来完成一些功能。在一些
pwn
题中,拿到靶机的shell后会发现能够使用的指令很少,应该就是因为靶机削减了一些不必要的的指令程序。
三、实验
0x00 启动xv6
按6.S081的指导,下载好xv6
的源代码后直接make qemu
就可以了
使用的系统是ubuntu 20.04,执行make qemu
时发现缺少了一些软件包,只需要使用apt
指令进行下载安装即可。
成功启动xv6:
0x01 sleep
这个实验要求编写一个sleep程序并在xv6中使用
达到使得进程暂停用户所输入的时间数的目的。
我的代码:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void main(int argc, char *argv[]) {
if(argc <= 1) {
fprintf(2, "sleep: invaild input.\n");
exit(0);
}
int tick = atoi(argv[1]);
if(tick < 0) {
fprintf(2, "sleep: invaild input:%s\n", argv[1]);
exit(0);
}
sleep(tick);
exit(0);
}
按照实验要求,将sleep.c
文件放入user文件夹
然后在Makefile的这个位置填写相关信息即可:
这里就是仅新增了一个条目:$U/_sleep\
效果如图所示:
0x02 pingpong
实验要求:
编写一个使用了unix系统调用的程序来实现两个进程之间的通信
父进程向子进程发送字节,子进程打印"<pid>: received ping"
,其中<pid>
是进程ID
然后子进程在管道中写入字节发送给父进程并推出
父进程读取从子进程发送来的字节然后打印"<pid>: received pong"
并推出
我的源代码:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
void main(int argc, char *argv[]) {
int fd[2]; // fd[0]:out fd[1]:in
int pid_s, pid;
char message[20];
if (pipe(fd) < 0) {
fprintf(2, "pingpong: exception in pipe\n");
}
if ((pid_s = fork()) < 0) {
fprintf(2, "pingpong: exception in fork");
}
else if (pid_s > 0) {
pid = getpid();
write(fd[1], "father", 7);
sleep(1);
while(read(fd[0], message, 4) <= 0);
fprintf(2, "<%d> received pong %s\n", pid, message);
close(fd[0]);
close(fd[1]);
exit(0);
}else {
pid = getpid();
read(fd[0], message, 7);
fprintf(2, "<%d>: received ping. %s\n", pid, message);
write(fd[1], "son", 4);
exit(0);
}
}
0x03 Primes
我的源代码:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
typedef unsigned long long piper;
piper newPipe() {
piper p;
pipe(((int *)&p));
return p;
}
void pipeWriteInt(piper pipe, int toWrite) {
write(((int *)&pipe)[1], &toWrite, 4);
}
int pipeReadInt(piper pipe) {
int toRet;
int result = read(((int *)&pipe)[0], &toRet, 4);
return result == 0 ? 0 : toRet;
}
void pipeClose(piper p) {
close(((int *)&p)[1]);
close(((int *)&p)[0]);
}
void pipeCloseRead(piper p) {
close(((int *)&p)[0]);
}
void pipeCloseWrite(piper p) {
close(((int *)&p)[1]);
}
int getPipeFirst(piper pipe) {
int t = pipeReadInt(pipe);
if(t == 0)exit(0);
fprintf(2, "prime %d\n", t);
return t;
}
void pipe_sender(piper lpipe, piper rpipe, int first) {
int i;
while((i = pipeReadInt(lpipe)) != 0) {
if(i % first != 0) {
pipeWriteInt(rpipe, i);
}
}
pipeCloseRead(lpipe);
pipeCloseWrite(rpipe);
}
void prime(piper lpipe) {
pipeCloseWrite(lpipe);
piper rpipe = newPipe();
int first = getPipeFirst(lpipe);
pipe_sender(lpipe, rpipe, first);
if(fork() == 0) {
prime(rpipe);
}else {
wait(0);
}
}
void main(int argc, char *argv[]) {
piper p = newPipe();
int i;
for(i = 2; i <= 35; i++) {
pipeWriteInt(p, i);
}
if(fork() == 0) {
prime(p);
}else {
pipeClose(p);
wait(0);
}
exit(0);
}
0x04 find
观察ls.c
文件,发现:
有一个stat
结构体,有一个fstate(fd, &st)
函数,能够让stat
结构体得到相关文件的一些信息。
能够分辨出这是一个文件还是文件夹,如果是文件夹,则继续递归查找;如果是文件,则判断是否为我们想要的文件。
0x05 xargs
0x06 实验总结
-
fork()
函数:创建一个新的进程,这个新的进程是完全复制当前进程的,且其也执行到父进程执行完毕fork()
函数的位置
并且,在父进程中,fork()
函数返回子进程的id
,而在子进程中,返回0,因此,可以利用这样的写法使得父进程与子进程的代码执行分离开:if(fork() == 0) { //子进程的代码 }else { //父进程的代码 }
-
pipe(int pip[2])
函数:创建一个管道,管道可以理解成一种特殊的文件,它不存在于硬盘上,只存在于内存中。pipe
函数的参数是一个长度为2的int
数组,这个函数会为数组中的两个元素赋值,是两个文件描述符。其中,pip[0]
是用于向管道中读取数据的文件描述符,pip[1]
是用于向管道写入数据的文件描述符。管道机制可以用于进程之间的通信。 -
wait()
函数:使一个进程停止,除非它的子进程消失,否则这个进程则会一直卡在执行到wait
函数的位置。