├── lab_5_guide.pdf ├── lab_5_guide ├── wm.png ├── wm-1559020714108.png ├── wm-1559020742914.png └── wm-1559020776745.png ├── lab_2 ├── 1555908590090.png ├── 1555908703986.png ├── 1555909301566.png ├── 1555909679300.png ├── 1555909735783.png ├── 1555910555122.png ├── 1555910588554.png ├── 1556429280359.png └── 1556429372443.png ├── lab_3 ├── 1557037618652.png ├── 1557037812290.png ├── 1557037975650.png ├── 1557037992440.png ├── 1557038011924.png ├── 1557038041740.png ├── 1557206500455.png ├── 1557289874947.png ├── 1557289976964.png ├── 1557290240208.png ├── 1557290496058.png ├── 1557379683507.png ├── 1557379813051.png ├── 1557379863183.png ├── 1557379959440.png └── 1557380010469.png ├── lab_4 ├── 1558759714290.png ├── 1558759807027.png ├── 1558761176972.png ├── 1558761319280.png ├── 1558762090240.png ├── 1558766841563.png ├── 1558767133961.png ├── 1558767153617.png ├── 1558767720772.png ├── 1558767750370.png ├── 1558767830278.png ├── 1558767868409.png ├── 1558767914486.png ├── 1558767948325.png ├── 1558768055154.png └── 1558768091683.png ├── lab_5 ├── 1559703605772.png ├── 1559703663259.png ├── 1559703792522.png ├── 1559703871603.png ├── 1559703895776.png ├── 1559704125361.png ├── 1559704186058.png ├── 1559704261444.png └── 1559704364924.png ├── lab_6 ├── 1561698914664.png ├── 1561699245393.png ├── 1561699326325.png ├── 深度截图_选择区域_20190729130246.png ├── 深度截图_选择区域_20190729130400.png └── 深度截图_选择区域_20190729130437.png ├── lab_7 ├── 2019-07-11 13-07-36屏幕截图.png ├── 10191360-6c638b96561fdb65.png ├── 10191360-fd5c58534c5db030.png ├── 深度截图_选择区域_20190711132339.png ├── 深度截图_选择区域_20190711132557.png ├── 深度截图_选择区域_20190712125327.png ├── 深度截图_选择区域_20190712130623.png ├── 深度截图_选择区域_20190712130955.png ├── 深度截图_选择区域_20190712131304.png ├── 深度截图_选择区域_20190712131514.png ├── 深度截图_选择区域_20190712131741.png ├── 深度截图_选择区域_20190712132135.png └── 深度截图_选择区域_20190712132422.png ├── lab_8 ├── 深度截图_选择区域_20190808124946.png ├── 深度截图_选择区域_20190808125116.png ├── 深度截图_选择区域_20190808125245.png ├── 深度截图_选择区域_20190808125349.png └── 深度截图_选择区域_20190808125455.png ├── README.md ├── lab_8.md ├── lab_3.md ├── lab_4.md ├── lab_2.md ├── lab_5.md ├── lab_5_guide.md ├── lab_6.md └── lab_7.md /lab_5_guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5_guide.pdf -------------------------------------------------------------------------------- /lab_5_guide/wm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5_guide/wm.png -------------------------------------------------------------------------------- /lab_2/1555908590090.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555908590090.png -------------------------------------------------------------------------------- /lab_2/1555908703986.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555908703986.png -------------------------------------------------------------------------------- /lab_2/1555909301566.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555909301566.png -------------------------------------------------------------------------------- /lab_2/1555909679300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555909679300.png -------------------------------------------------------------------------------- /lab_2/1555909735783.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555909735783.png -------------------------------------------------------------------------------- /lab_2/1555910555122.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555910555122.png -------------------------------------------------------------------------------- /lab_2/1555910588554.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1555910588554.png -------------------------------------------------------------------------------- /lab_2/1556429280359.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1556429280359.png -------------------------------------------------------------------------------- /lab_2/1556429372443.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_2/1556429372443.png -------------------------------------------------------------------------------- /lab_3/1557037618652.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557037618652.png -------------------------------------------------------------------------------- /lab_3/1557037812290.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557037812290.png -------------------------------------------------------------------------------- /lab_3/1557037975650.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557037975650.png -------------------------------------------------------------------------------- /lab_3/1557037992440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557037992440.png -------------------------------------------------------------------------------- /lab_3/1557038011924.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557038011924.png -------------------------------------------------------------------------------- /lab_3/1557038041740.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557038041740.png -------------------------------------------------------------------------------- /lab_3/1557206500455.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557206500455.png -------------------------------------------------------------------------------- /lab_3/1557289874947.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557289874947.png -------------------------------------------------------------------------------- /lab_3/1557289976964.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557289976964.png -------------------------------------------------------------------------------- /lab_3/1557290240208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557290240208.png -------------------------------------------------------------------------------- /lab_3/1557290496058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557290496058.png -------------------------------------------------------------------------------- /lab_3/1557379683507.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557379683507.png -------------------------------------------------------------------------------- /lab_3/1557379813051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557379813051.png -------------------------------------------------------------------------------- /lab_3/1557379863183.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557379863183.png -------------------------------------------------------------------------------- /lab_3/1557379959440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557379959440.png -------------------------------------------------------------------------------- /lab_3/1557380010469.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_3/1557380010469.png -------------------------------------------------------------------------------- /lab_4/1558759714290.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558759714290.png -------------------------------------------------------------------------------- /lab_4/1558759807027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558759807027.png -------------------------------------------------------------------------------- /lab_4/1558761176972.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558761176972.png -------------------------------------------------------------------------------- /lab_4/1558761319280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558761319280.png -------------------------------------------------------------------------------- /lab_4/1558762090240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558762090240.png -------------------------------------------------------------------------------- /lab_4/1558766841563.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558766841563.png -------------------------------------------------------------------------------- /lab_4/1558767133961.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767133961.png -------------------------------------------------------------------------------- /lab_4/1558767153617.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767153617.png -------------------------------------------------------------------------------- /lab_4/1558767720772.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767720772.png -------------------------------------------------------------------------------- /lab_4/1558767750370.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767750370.png -------------------------------------------------------------------------------- /lab_4/1558767830278.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767830278.png -------------------------------------------------------------------------------- /lab_4/1558767868409.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767868409.png -------------------------------------------------------------------------------- /lab_4/1558767914486.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767914486.png -------------------------------------------------------------------------------- /lab_4/1558767948325.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558767948325.png -------------------------------------------------------------------------------- /lab_4/1558768055154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558768055154.png -------------------------------------------------------------------------------- /lab_4/1558768091683.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_4/1558768091683.png -------------------------------------------------------------------------------- /lab_5/1559703605772.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559703605772.png -------------------------------------------------------------------------------- /lab_5/1559703663259.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559703663259.png -------------------------------------------------------------------------------- /lab_5/1559703792522.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559703792522.png -------------------------------------------------------------------------------- /lab_5/1559703871603.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559703871603.png -------------------------------------------------------------------------------- /lab_5/1559703895776.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559703895776.png -------------------------------------------------------------------------------- /lab_5/1559704125361.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559704125361.png -------------------------------------------------------------------------------- /lab_5/1559704186058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559704186058.png -------------------------------------------------------------------------------- /lab_5/1559704261444.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559704261444.png -------------------------------------------------------------------------------- /lab_5/1559704364924.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5/1559704364924.png -------------------------------------------------------------------------------- /lab_6/1561698914664.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/1561698914664.png -------------------------------------------------------------------------------- /lab_6/1561699245393.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/1561699245393.png -------------------------------------------------------------------------------- /lab_6/1561699326325.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/1561699326325.png -------------------------------------------------------------------------------- /lab_5_guide/wm-1559020714108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5_guide/wm-1559020714108.png -------------------------------------------------------------------------------- /lab_5_guide/wm-1559020742914.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5_guide/wm-1559020742914.png -------------------------------------------------------------------------------- /lab_5_guide/wm-1559020776745.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_5_guide/wm-1559020776745.png -------------------------------------------------------------------------------- /lab_7/2019-07-11 13-07-36屏幕截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/2019-07-11 13-07-36屏幕截图.png -------------------------------------------------------------------------------- /lab_6/深度截图_选择区域_20190729130246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/深度截图_选择区域_20190729130246.png -------------------------------------------------------------------------------- /lab_6/深度截图_选择区域_20190729130400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/深度截图_选择区域_20190729130400.png -------------------------------------------------------------------------------- /lab_6/深度截图_选择区域_20190729130437.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_6/深度截图_选择区域_20190729130437.png -------------------------------------------------------------------------------- /lab_7/10191360-6c638b96561fdb65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/10191360-6c638b96561fdb65.png -------------------------------------------------------------------------------- /lab_7/10191360-fd5c58534c5db030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/10191360-fd5c58534c5db030.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190711132339.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190711132339.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190711132557.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190711132557.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712125327.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712125327.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712130623.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712130623.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712130955.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712130955.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712131304.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712131304.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712131514.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712131514.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712131741.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712131741.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712132135.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712132135.png -------------------------------------------------------------------------------- /lab_7/深度截图_选择区域_20190712132422.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_7/深度截图_选择区域_20190712132422.png -------------------------------------------------------------------------------- /lab_8/深度截图_选择区域_20190808124946.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_8/深度截图_选择区域_20190808124946.png -------------------------------------------------------------------------------- /lab_8/深度截图_选择区域_20190808125116.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_8/深度截图_选择区域_20190808125116.png -------------------------------------------------------------------------------- /lab_8/深度截图_选择区域_20190808125245.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_8/深度截图_选择区域_20190808125245.png -------------------------------------------------------------------------------- /lab_8/深度截图_选择区域_20190808125349.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_8/深度截图_选择区域_20190808125349.png -------------------------------------------------------------------------------- /lab_8/深度截图_选择区域_20190808125455.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iLoveTangY/hit-oslab/HEAD/lab_8/深度截图_选择区域_20190808125455.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 关于本仓库 2 | 3 | 本仓库为网易云课堂李志军老师[操作系统]()课程的实验报告。 4 | 5 | 所有实验完成过程中大量参考实验楼上别的同学的实验报告,在此表示感谢。 6 | 7 | 如有错误,欢迎提Issue或者通过邮箱(ytang007@163.com)联系本人。 8 | 9 | # 实验环境搭建 10 | 11 | 参考[这里]()和[这里]()。实验手册也能从这两个链接中下载到。 12 | 13 | # 进度 14 | 15 | 实验名称抄袭自[实验楼]()上的实验名称... 16 | 17 | - [x] [实验二 操作系统的引导](./lab_2.md) 18 | 19 | - [x] [实验三 系统调用](./lab_3.md) 20 | 21 | - [x] [实验四 进程运行轨迹的跟踪与统计](./lab_4.md) 22 | 23 | - [x] [实验五 基于内核栈切换的进程切换](lab_5.md) 24 | 25 | - [x] [实验六 信号量的实现与应用](./lab_6.md) 26 | 27 | - [x] [实验七 地址映射与共享](./lab_7.md) 28 | 29 | - [x] [实验八 终端设备的控制](./lab_8.md) 30 | 31 | - [ ] lab_9 32 | 33 | # 参考资料 34 | 35 | * [操作系统课程]() 36 | * [Linux0.11 内核完全注释]() 37 | 38 | -------------------------------------------------------------------------------- /lab_8.md: -------------------------------------------------------------------------------- 1 | # 修改代码,实现按下F12之后替换字母 2 | 3 | 很明显,我们得找到按下F12之后调用的是哪个过程,通过查看《注释》,在`keyboard.S`中找到下面的位置: 4 | 5 | ![深度截图_选择区域_20190808124946](lab_8/深度截图_选择区域_20190808124946.png) 6 | 7 | 代码的注释中也提到这个函数是功能键按下后调用的函数。因此在这里我们加上自己的函数调用代码,也就是`change_flag`这个函数。这个函数我们在`console.c`中定义如下: 8 | 9 | ![深度截图_选择区域_20190808125116](lab_8/深度截图_选择区域_20190808125116.png) 10 | 11 | 也非常简单。然后我们在`con_write`函数中判断F12是否按下,如果按下就将回显字符设置为`*`,代码如下: 12 | 13 | ![深度截图_选择区域_20190808125245](lab_8/深度截图_选择区域_20190808125245.png) 14 | 15 | 注意,我们只将字母替换为了`*`。 16 | 17 | 实验结果: 18 | 19 | 按下之前: 20 | 21 | ![深度截图_选择区域_20190808125349](lab_8/深度截图_选择区域_20190808125349.png) 22 | 23 | 按下F12之后: 24 | 25 | ![深度截图_选择区域_20190808125455](lab_8/深度截图_选择区域_20190808125455.png) 26 | 27 | 可以看到所有的输出字母都变成了星号。 28 | 29 | 可能又得同学注意到了,除了星号,还输出了一段进程信息,包括pid,state等。这就是F12默认的作用。 30 | 31 | # 回答问题 32 | 33 | 1. 在原始代码中,按下F12,中断响应后,中断服务程序会调用func?它实现的是什么功能? 34 | 35 | 答:上面已经提到,原始的F12的功能是打印出进程状态信息。 36 | 37 | 2. 在你的实现中,是否把向文件输出的字符也过滤了?如果是,那么怎么能只过滤向终端输出的字符?如果不是,那么怎么能把向文件输出的字符也一并进行过滤? 38 | 39 | 答:没有过滤掉向文件输出的字符,因为只修改了`con_write`函数。向文件中输出字符调用的是`file_dev`中的`file_write`函数来实现的。因此,如果要修改像文件中输出的字符,就可以修改这个`file_write`函数。或者在上层往缓冲区队列放入字符的时候就将字符替换成星号。 -------------------------------------------------------------------------------- /lab_3.md: -------------------------------------------------------------------------------- 1 | # 添加`iam`和`whoami`系统调用 2 | 3 | 按照实验指导书的参考,首先在`kernel/system_call.s`中的系统调用总数为`74`: 4 | 5 | ![1557037618652](lab_3/1557037618652.png) 6 | 7 | 然后在`include/unistd.h`中添加`iam`和`whoami`系统调用编号的宏定义: 8 | 9 | ![1557037812290](lab_3/1557037812290.png) 10 | 11 | 在`include/linux/sys.h`中的系统调用函数表中增加系统调用条目: 12 | 13 | ![1557037992440](lab_3/1557037992440.png) 14 | 15 | ![1557038041740](lab_3/1557038041740.png) 16 | 17 | 按照实验要求,我们在`kernel`目录中增加`who.c`这个文件,在这个文件里真正实现两个系统调用,为了验证我们前面的修改是否成功,我们先简单的在系统调用中输出一些文字: 18 | 19 | ![1557206500455](lab_3/1557206500455.png) 20 | 21 | 按照指导书中所说修改`kernel`文件夹中的`Makefile`: 22 | 23 | * 增加`who.o`这个依赖 24 | 25 | ![1557289874947](lab_3/1557289874947.png) 26 | 27 | * 在Dependencies中增加对`who.s`和`who.o`的依赖产生条件: 28 | 29 | ![1557289976964](lab_3/1557289976964.png) 30 | 31 | 然后按照以前的方式`make all`,再执行`./run`进入Linux0.11子系统,在`/usr/include/unistd.h`中增加`iam`和`whoami`的宏定义: 32 | 33 | ![1557290240208](lab_3/1557290240208.png) 34 | 35 | 然后可以编写一个测试程序来测试下我们的系统调用是否真的已经添加成功了。我们写一个`hello.c`: 36 | 37 | ```c 38 | #define __LIBRARY__ /* 必须定义这个宏 */ 39 | #include 40 | 41 | /* iam 和 whoami 系统调用的用户接口 */ 42 | _syscall1(int, iam, const char*, name); 43 | _syscall2(int, whoami, char*, name, unsigned int, size); 44 | 45 | int main(void) 46 | { 47 | char buf[24]; 48 | iam("test"); 49 | whoami(buf, 24); 50 | return 0; 51 | } 52 | ``` 53 | 54 | 编译执行: 55 | 56 | ![1557290496058](lab_3/1557290496058.png) 57 | 58 | 说明我们自己的系统调用已经成功的添加了。 59 | 60 | # 实现`iam`和`whoami`系统调用 61 | 62 | 根据提示,使用`get_fs_byte`和`put_fs_byte`很容易将用户空间中的数据拷贝到内核空间中,编写整个程序的过程中要有这样一个意识:目前我们处于内核空间中,而`name`处于用户空间,任何涉及到取用`name`的操作都需要通过`get_fs_byte`和`put_fs_byte`来进行,不能直接使用用户空间的数据。其他的注意点在代码中都有体现。整个`who.c`实现代码如下: 63 | 64 | ![1557379683507](lab_3/1557379683507.png) 65 | 66 | # 测试 67 | 68 | 在linux0.11子系统中编写`iam.c`和`whoami.c`如下: 69 | 70 | ![iam.c](lab_3/1557379813051.png) 71 | 72 | ![whoami.c](lab_3/1557379863183.png) 73 | 74 | 然后编译执行`testlab2.c`,结果如下: 75 | 76 | ![1557379959440](lab_3/1557379959440.png) 77 | 78 | 执行testlab2.sh,结果如下: 79 | 80 | ![1557380010469](lab_3/1557380010469.png) 81 | 82 | 获得了全部的80%的分数。 83 | 84 | # 回答问题 85 | 86 | > 从Linux 0.11现在的机制看,它的系统调用最多能传递几个参数?你能想出办法来扩大这个限制吗? 87 | 88 | 从目前的Linux 0.11的实现机制来看,最多只能传递三个参数,因为内核中只定义了`_syscall1`、`_syscall2`、`_syscall3`三个宏。当然,你可能想到我们可以自己编写更多的传递更多的参数的`_syscall*`,但是由于32位的处理器中只有`eax`、`ebx`、`ecx`、`edx`四个寄存器,其中`eax`用来传递中断号,只有其他三个寄存器能够用来传递参数。因此这种方法并不能真正解决问题。要想解决这个问题,只有改变参数传递的方式,比如参考一般的过程,用栈来传递参数。 89 | 90 | > 用文字简要描述向Linux 0.11添加一个系统调用foo()的步骤。 91 | 92 | * 在`kernel/system_call.s`中修改系统调用总数 93 | * 然后在`include/unistd.h`中添加`iam`和`whoami`系统调用编号的宏定义 94 | * 在`include/linux/sys.h`中的系统调用函数表中增加系统调用条目 95 | * 最后在内核的某个文件中真正实现这个系统调用 -------------------------------------------------------------------------------- /lab_4.md: -------------------------------------------------------------------------------- 1 | # 编写多进程样本程序 2 | 3 | > 基于模板“process.c”编写多进程的样本程序,实现如下功能: 4 | > i. 所有子进程都并行运行,每个子进程的实际运行时间一般不超过30秒; 5 | > ii. 父进程向标准输出打印所有子进程的id,并在所有子进程都退出后才退出; 6 | 7 | 这里我们主要使用`fork`和`wait`系统调用来完成题目要求: 8 | 9 | ```c 10 | #include 11 | #include 12 | #include 13 | 14 | pid_t fork(void); 15 | 16 | pid_t wait(int *wstatus); 17 | ``` 18 | 19 | `fork`函数创建一个子进程。子进程的进程内存空间和父进程中的完全一样,并且从`fork`之后的地方开始执行。`fork`函数在父进程中返回子进程的`pid`,在子进程中返回`0`,出错时返回`-1`,并设置`errno`。 20 | 21 | `wait`函数等待当前进程的子进程终止(只要有一个终止,`wait`系统调用就会返回),如果没有子进程终止,则会阻塞。如果成功,返回终止的子进程的`pid`,失败返回`-1`。 22 | 23 | 详细信息请`man fork`和`man wait`,或者Google。 24 | 25 | 实现代码如下: 26 | 27 | ```c 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | /* 一个子进程的运行时间(s) */ 37 | #define CHILD_RUN_TIME 30 38 | /* 子进程数量 */ 39 | #define CHILD_PROCESS_NUM 4 40 | 41 | #define HZ 100 42 | 43 | void cpuio_bound(int last, int cpu_time, int io_time); 44 | 45 | int main(void) 46 | { 47 | pid_t pid; 48 | int i = 0; 49 | 50 | while (i < CHILD_PROCESS_NUM) 51 | { 52 | if ((pid = fork()) < 0) 53 | { 54 | fprintf(stderr, "Error in fork()\n"); 55 | return -1; 56 | } 57 | else if (pid == 0) /* in child */ 58 | { 59 | /* 子进程执行指定时间后退出 */ 60 | cpuio_bound(CHILD_RUN_TIME, 1, 1); 61 | exit(0); 62 | } 63 | else /* in parent */ 64 | { 65 | fprintf(stdout, "Process %lu created.\n", (long)(pid)); 66 | ++i; 67 | } 68 | } 69 | 70 | /* 父进程中一直要等待所有的子进程退出 */ 71 | while ((pid = wait(NULL)) != -1) 72 | { 73 | fprintf(stdout, "Process %lu terminated.\n", (long)(pid)); 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | /* 80 | * 此函数按照参数占用CPU和I/O时间 81 | * last: 函数实际占用CPU和I/O的总时间,不含在就绪队列中的时间,>=0是必须的 82 | * cpu_time: 一次连续占用CPU的时间,>=0是必须的 83 | * io_time: 一次I/O消耗的时间,>=0是必须的 84 | * 如果last > cpu_time + io_time,则往复多次占用CPU和I/O 85 | * 所有时间的单位为秒 86 | */ 87 | void cpuio_bound(int last, int cpu_time, int io_time) 88 | { 89 | struct tms start_time, current_time; 90 | clock_t utime, stime; 91 | int sleep_time; 92 | 93 | while (last > 0) 94 | { 95 | /* CPU Burst */ 96 | times(&start_time); 97 | /* 其实只有t.tms_utime才是真正的CPU时间。但我们是在模拟一个 98 | * 只在用户状态运行的CPU大户,就像“for(;;);”。所以把t.tms_stime 99 | * 加上很合理。*/ 100 | do 101 | { 102 | times(¤t_time); 103 | utime = current_time.tms_utime - start_time.tms_utime; 104 | stime = current_time.tms_stime - start_time.tms_stime; 105 | } while ( ( (utime + stime) / HZ ) < cpu_time ); 106 | last -= cpu_time; 107 | 108 | if (last <= 0 ) 109 | break; 110 | 111 | /* IO Burst */ 112 | /* 用sleep(1)模拟1秒钟的I/O操作 */ 113 | sleep_time=0; 114 | while (sleep_time < io_time) 115 | { 116 | sleep(1); 117 | sleep_time++; 118 | } 119 | last -= sleep_time; 120 | } 121 | } 122 | ``` 123 | 124 | # 实现进程运行轨迹的跟踪 125 | 126 | > 在Linux 0.11上实现进程运行轨迹的跟踪。基本任务是在内核中维护一个日志文件/var/process.log,把从操作系统启动到系统关机过程中所有进程的运行轨迹都记录在这一log文件中。 127 | 128 | 其实这个任务就是要在进程状态发生变化时将状态的变化记录到日志文件中,按照实验手册的指导很容易完成。 129 | 130 | 先**修改`main.c`中的`main()`函数**,将一下代码从`init`函数中移到`main`中: 131 | 132 | ![1558766841563](lab_4/1558766841563.png) 133 | 134 | 按照实验指导书上所说**在`kernel/printk.c`中添加`fprintk`函数**; 135 | 136 | 然后就是跟踪进程的运行轨迹了,主要修改以下的文件: 137 | 138 | **`kernel/fork.c`中的`copy_process`里两处地方:** 139 | 140 | ![1558767133961](lab_4/1558767133961.png) 141 | 142 | ![1558767153617](lab_4/1558767153617.png) 143 | 144 | **接下来是`kernel/sched.c`中:** 145 | 146 | **`schedule`函数中:** 147 | 148 | ![1558767720772](lab_4/1558767720772.png) 149 | 150 | ![1558767750370](lab_4/1558767750370.png) 151 | 152 | **`sys_pause`函数中:** 153 | 154 | ![1558767830278](lab_4/1558767830278.png) 155 | 156 | **`sleep_on`函数中**: 157 | 158 | ![1558767868409](lab_4/1558767868409.png) 159 | 160 | **`interruptible_sleep_on`函数中:** 161 | 162 | ![1558767914486](lab_4/1558767914486.png) 163 | 164 | **`wake_up`函数中:** 165 | 166 | ![1558767948325](lab_4/1558767948325.png) 167 | 168 | **修改`kernel/exit.c`中的几个函数:** 169 | 170 | **`do_exit`函数中:** 171 | 172 | ![1558768055154](lab_4/1558768055154.png) 173 | 174 | **`sys_waitpid`函数中:** 175 | 176 | ![1558768091683](lab_4/1558768091683.png) 177 | 178 | 至此,所有的进程状态转移信息我们就已经记录到了日志文件中,不过要注意一点,写入信息是要判断进程状态是否真正的发生了改变。 179 | 180 | # 统计进程时间 181 | 182 | > 在修改过的0.11上运行样本程序,通过分析log文件,统计该程序建立的所有进程的等待时间、完成时间(周转时间)和运行时间,然后计算平均等待时间,平均完成时间和吞吐量。可以自己编写统计程序,也可以使用python脚本程序——stat_log.py ——进行统计。 183 | 184 | 我们将上面实现的`process.c`拷贝到Linux 0.11下编译运行,生成日志文件,然后利用`stat_log.py`统计程序运行时间结果如下: 185 | 186 | ![1558761176972](lab_4/1558761176972.png) 187 | 188 | # 修改0.11进程调度的时间片 189 | 190 | > 修改0.11进程调度的时间片,然后再运行同样的样本程序,统计同样的时间数据,和原有的情况对比,体会不同时间片带来的差异。 191 | 192 | 修改时间片有几种方式,可以修改`sched.h`中`#define HZ 100`,这个表示系统时钟滴答的频率。也可以修改指导书中提到的`INIT_TASK`宏。 193 | 194 | 我们按照指导书中的提示修改`INIT_TASK`宏中的`counter`以增加每个进程的时间片。我这里将`counter`由15改为30,结果如下: 195 | 196 | ![1558762090240](lab_4/1558762090240.png) 197 | 198 | 可以发现,由于每个进程能够执行的时间更长了,大部分进程的轮转时间(Turnaround)是降低了的。整体的吞吐量也有提升,但是对于我们的`process.c`这个程序来说,由于是个CPU和I/O任务都很均衡的进程,所以效果不明显。 -------------------------------------------------------------------------------- /lab_2.md: -------------------------------------------------------------------------------- 1 | # 改写`bootsect.s` 2 | 3 | > bootsect.s能在屏幕上打印一段提示信息“XXX is booting...”,其中XXX是你给自己的操作系统起的名字,例如LZJos、Sunix等(可以上论坛上秀秀谁的OS名字最帅,也可以显示一个特色logo,以表示自己操作系统的与众不同。) 4 | 5 | 很简单,修改下`msg1`这个段的显示文字: 6 | 7 | ![1555908590090](lab_2/1555908590090.png) 8 | 9 | 然后再修改下`print`时的message长度即可: 10 | 11 | ![1555908703986](lab_2/1555908703986.png) 12 | 13 | 特色的LOGO可以使用`banner`这个命令,将文字转成ASCII字符画。 14 | 15 | ![1555910555122](lab_2/1555910555122.png) 16 | 17 | 至于这个长度怎么计算出来呢?当然不能一个一个数啦,这样不得累死。Linux中有个命令叫`wc`,可以计算字符个数,因此我们如下来计算字符个数: 18 | 19 | ![1555910588554](lab_2/1555910588554.png) 20 | 21 | 8和16分别表示行数和字符数,`wc`命令统计的字符数包括换行符,但是这里换行符只是一个字节,因此这里我们要在160的基础上再加上一个8(Linux0.11内核中换行符用两个字节来表示),另外,最后再加上两个字节表示最后额外的一个换行符。所以,总共的字节数就是$160+8+2=170$。 22 | 23 | 实验结果: 24 | 25 | ![1555909301566](lab_2/1555909301566.png) 26 | 27 | # 改写`setup.s` 28 | 29 | ## 改写`setup.s`向屏幕输出一行"Now we are in SETUP" 30 | 31 | > bootsect.s能完成setup.s的载入,并跳转到setup.s开始地址执行。而setup.s向屏幕输出一行"Now we are in SETUP"。 32 | 33 | 我们可以参照着`bootsect.s`中输出文字的代码自己写个`setup.s`,只需要修改下`es`中存储的段偏移地址即可: 34 | 35 | ```assembly 36 | ! 37 | ! SYS_SIZE is the number of clicks (16 bytes) to be loaded. 38 | ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current 39 | ! versions of linux 40 | ! 41 | SYSSIZE = 0x3000 42 | ! 43 | ! bootsect.s (C) 1991 Linus Torvalds 44 | ! 45 | ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves 46 | ! iself out of the way to address 0x90000, and jumps there. 47 | ! 48 | ! It then loads 'setup' directly after itself (0x90200), and the system 49 | ! at 0x10000, using BIOS interrupts. 50 | ! 51 | ! NOTE! currently system is at most 8*65536 bytes long. This should be no 52 | ! problem, even in the future. I want to keep it simple. This 512 kB 53 | ! kernel size should be enough, especially as this doesn't contain the 54 | ! buffer cache as in minix 55 | ! 56 | ! The loader has been made as simple as possible, and continuos 57 | ! read errors will result in a unbreakable loop. Reboot by hand. It 58 | ! loads pretty fast by getting whole sectors at a time whenever possible. 59 | 60 | .globl begtext, begdata, begbss, endtext, enddata, endbss 61 | .text 62 | begtext: 63 | .data 64 | begdata: 65 | .bss 66 | begbss: 67 | .text 68 | 69 | SETUPSEG = 0x9020 ! setup starts here 70 | 71 | entry _start 72 | _start: 73 | mov ax, #SETUPSEG ! 修改这里就行 74 | mov es, ax 75 | ! Print some inane message 76 | mov ah,#0x03 ! read cursor pos 77 | xor bh,bh 78 | int 0x10 79 | 80 | mov cx,#25 81 | mov bx,#0x0007 ! page 0, attribute 7 (normal) 82 | mov bp,#msg1 83 | mov ax,#0x1301 ! write string, move cursor 84 | int 0x10 85 | 86 | msg1: 87 | .byte 13,10 88 | .ascii "Now we are in setup" 89 | .byte 13,10,13,10 90 | 91 | .text 92 | endtext: 93 | .data 94 | enddata: 95 | .bss 96 | endbss: 97 | ``` 98 | 99 | 然后为了使用`make`编译,我们修改下build.c,当第三个参数为`"none"`的时候直接返回即可: 100 | 101 | ![1555909679300](lab_2/1555909679300.png) 102 | 103 | 最后使用`make BootImage`命令编译,运行: 104 | 105 | ![1555909735783](lab_2/1555909735783.png) 106 | 107 | ## 改写`setup.s`输出硬件参数 108 | 109 | > setup.s能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。 110 | 111 | 根据实验手册,我们先写出获取光标位置、扩展内存大小以及磁盘参数表的代码。照抄实验手册即可,这里不再给出。然后按照实验手册上的提示写出打印16位数的汇编过程,注意这里写的是个过程,也就是函数,需要使用`call`指令来调用。为了方便起见,我这里将打印的数放在了`dx`寄存器中: 112 | 113 | ```assembly 114 | !以16进制的方式打印dx中存放的16位整数 115 | print_hex: 116 | mov cx, #4 117 | !mov dx, (bp) !这里已注释,调用时确保dx寄存器中已经存放要打印的数 118 | print_digit: 119 | rol dx, #4 !循环右移4位 120 | mov ax, #0xe0f 121 | and al, dl 122 | add al, #0x30 !得到整数的ASCI码 123 | cmp al, #0x3a 124 | jl outp 125 | add al, #0x07 126 | outp: 127 | int 0x10 128 | loop print_digit 129 | ret 130 | ``` 131 | 132 | 为了方便起见,我们也可以将打印文字提示信息的代码也封装成一个过程: 133 | 134 | ```assembly 135 | !打印bp中指定的message,cx中应该存放字符数 136 | print_msg: 137 | mov bx, #0x0007 138 | mov ax, #0x1301 139 | int 0x10 140 | ret 141 | ``` 142 | 143 | 注意,在`call print_msg`之前应该先获取光标位置,然后设定好`bp`、`cx`寄存器。然后利用这两个过程和实验手册上给出的获取硬件信息的代码,我们就可以写出符合实验手册要求的程序了。完整程序如下: 144 | 145 | ```assembly 146 | .globl begtext, begdata, begbss, endtext, enddata, endbss 147 | .text 148 | begtext: 149 | .data 150 | begdata: 151 | .bss 152 | begbss: 153 | .text 154 | 155 | INITSEG = 0x9000 156 | SYSSEG = 0x1000 157 | SETUPSEG = 0x9020 ! setup starts here 158 | 159 | entry _start 160 | _start: 161 | mov ax, #SETUPSEG 162 | mov es, ax 163 | ! Print some inane message 164 | !读入光标位置 165 | mov ah,#0x03 ! read cursor pos 166 | xor bh,bh 167 | int 0x10 168 | 169 | mov cx,#25 170 | mov bp,#msg1 171 | call print_msg 172 | 173 | !读入光标位置 174 | mov ax, #INITSEG 175 | mov ds, ax 176 | mov ah, #0x03 !功能号0x03表示读光标 177 | xor bh, bh 178 | int 0x10 179 | mov [0], dx !将dx中保存的光标位置保存到ds:0中 180 | !打印光标位置 181 | mov cx, #14 182 | mov bp, #msg2 183 | call print_msg 184 | call print_hex 185 | call print_nl 186 | !读入内存大小 187 | mov ah, #0x88 188 | int 0x15 189 | mov [2], ax 190 | !打印扩展内存大小 191 | mov cx, #15 192 | mov bp, #msg3 193 | call print_msg 194 | mov dx, [2] 195 | call print_hex 196 | call print_nl 197 | 198 | !从0x41处拷贝16个字节(第一个磁盘参数表) 199 | mov ax, #0x000 200 | mov ds, ax 201 | lds si, [4*0x41] !第一个磁盘参数表的入口地址 202 | mov ax, #INITSEG 203 | mov es, ax 204 | mov di, #0x0004 !从es:0x0004处开始存储 205 | mov cx, #0x10 206 | rep 207 | movsb 208 | 209 | mov ax, #INITSEG 210 | mov ds, ax 211 | mov ax, #SETUPSEG 212 | mov es, ax 213 | 214 | mov ah,#0x03 ! read cursor pos 215 | xor bh,bh 216 | int 0x10 217 | !打印柱面数 218 | mov cx, #8 219 | mov bp, #msg4 220 | call print_msg 221 | mov dx,[4] 222 | call print_hex 223 | call print_nl 224 | 225 | mov ah,#0x03 ! read cursor pos 226 | xor bh,bh 227 | int 0x10 228 | !打印磁头数 229 | mov cx, #9 230 | mov bp, #msg5 231 | call print_msg 232 | mov dx, [6] 233 | call print_hex 234 | call print_nl 235 | 236 | mov ah, #0x03 237 | xor bh, bh 238 | int 0x10 239 | !打印每磁道扇区数 240 | mov cx, #11 241 | mov bp, #msg6 242 | call print_msg 243 | mov dx, [18] 244 | call print_hex 245 | call print_nl 246 | !在这儿进入死循环,不再加载Linux内核 247 | pause: 248 | jmp pause 249 | 250 | !以16进制的方式打印dx中的16位数 251 | print_hex: 252 | mov cx, #4 253 | print_digit: 254 | rol dx, #4 !循环右移4位 255 | mov ax, #0xe0f 256 | and al, dl 257 | add al, #0x30 !得到整数的ASCI码 258 | cmp al, #0x3a 259 | jl outp 260 | add al, #0x07 261 | outp: 262 | int 0x10 263 | loop print_digit 264 | ret 265 | 266 | print_nl: 267 | mov ax, #0xe0d 268 | int 0x10 269 | mov al, #0xa 270 | int 0x10 271 | ret 272 | 273 | !打印bp中指定的message,cx中应该存放字符数 274 | print_msg: 275 | mov bx, #0x0007 276 | mov ax, #0x1301 277 | int 0x10 278 | ret 279 | 280 | msg1: 281 | .byte 13,10 282 | .ascii "Now we are in setup" 283 | .byte 13,10,13,10 284 | msg2: 285 | .byte 13,10 286 | .ascii "Cursor Pos: " 287 | msg3: 288 | .byte 13,10 289 | .ascii "Memory Size: " 290 | msg4: 291 | .byte 13,10 292 | .ascii "Cyls: " 293 | msg5: 294 | .byte 13,10 295 | .ascii "Heads: " 296 | msg6: 297 | .byte 13,10 298 | .ascii "Sectors: " 299 | 300 | .text 301 | endtext: 302 | .data 303 | enddata: 304 | .bss 305 | endbss: 306 | ``` 307 | 308 | 至于其它一些信息的获取可以参考Linux内核完全注释,信息的打印都是一样的,这里不再给出。 309 | 310 | 实验结果: 311 | 312 | ![1556429280359](lab_2/1556429280359.png) 313 | 314 | 根据bochsrc.bxrc中的内容: 315 | 316 | ![1556429372443](lab_2/1556429372443.png) 317 | 318 | 结果完全正确。 -------------------------------------------------------------------------------- /lab_5.md: -------------------------------------------------------------------------------- 1 | **[注意]**:实验指导书中没有本次试验,但是实验楼中有,因此我将本次实验的实验指导下载下来放在了[这里](./lab_5_guide.pdf),可以参考一下。 2 | 3 | # `shedule`与`switch_to` 4 | 5 | 先在`sched.h`中注释掉现有的`switch_to`: 6 | 7 | ![1559703663259](lab_5/1559703663259.png) 8 | 9 | 在`sched.c`中增加对`switch_to`函数的声明: 10 | 11 | ![1559703605772](lab_5/1559703605772.png) 12 | 13 | 修改`schedule`函数: 14 | 15 | ![1559703792522](lab_5/1559703792522.png) 16 | 17 | ![1559703871603](lab_5/1559703871603.png) 18 | 19 | ![1559703895776](lab_5/1559703895776.png) 20 | 21 | 注意,这里`pnext`必须得赋一个初值。 22 | 23 | # 实现`switch_to` 24 | 25 | 接下来就是真正实现`switch_to`了。在`system_call.s`添加`switch_to`过程的汇编实现。具体原理参考指导书,代码见下,注释已经写的很详细了: 26 | 27 | ```assembly 28 | .align 2 29 | switch_to: 30 | pushl %ebp # 保存调用此函数的函数的栈帧基地址 31 | movl %esp, %ebp # 当前函数(switch_to)的栈帧基地址为栈顶地址 32 | pushl %ecx 33 | pushl %ebx 34 | pushl %eax # 以上保存下面用到的几个寄存器 35 | movl 8(%ebp), %ebx # 调用switch_to的第一个参数,即pnext——目标进程的PCB 36 | cmpl %ebx, current # current为全局变量,指向当前进程的PCB 37 | je 1f # 如果要切换到的进程就是目标进程,则不需要做任何操作 38 | 39 | # 切换PCB 40 | movl %ebx, %eax 41 | xchgl %eax, current # eax指向当前进程,ebx指向下一个进程,current指向下一个进程 42 | 43 | # TSS中的内核栈基地址指针(esp)重写(处理器处理中断时会用到TSS中的内核栈指针来恢复内核栈位置,即设置内核栈的ebp) 44 | movl tss, %ecx # tss为全局变量,指向当前进程的tss,以后所有进程都用这个tss,任务切换时不再像以前一样发生变化 45 | addl $4096, %ebx # 加上4096是因为如实验指导书所说,内核栈栈底位于PCB所在内存的高地址空间 46 | movl %ebx, ESP0(%ecx) 47 | 48 | # 切换内核栈栈顶指针(切换当前的内核栈为目标内核栈),即保存当前内核栈用到了哪个位置 49 | movl %esp, KERNEL_STACK(%eax) # 保存当前进程内核栈栈顶指针到PCB中,注意,上面已经将eax指向了当前进程的PCB 50 | movl 8(%ebp), %ebx # 注意,这里取出的是下一个进程的PCB地址 51 | movl KERNEL_STACK(%ebx), %esp # 通过PCB地址可以获得之前保存的内核栈栈顶指针位置 52 | 53 | # 切换LDT 54 | movl 12(%ebp), %ecx 55 | lldt %cx 56 | 57 | movl $0x17, %ecx 58 | mov %cx, %fs # 为啥??目的是修改一下fs的值,会重新加载段寄存器的隐式部分 59 | cmpl %eax, last_task_used_math 60 | jne 1f 61 | clts 62 | 1: popl %eax # 以下恢复函数开始时保存的寄存器,注意,这里已经切换到了另一个进程的内核栈,所以这些参数进程中开始的时候需要保存下来 63 | popl %ebx 64 | popl %ecx 65 | popl %ebp # 恢复栈帧基地址 66 | ret 67 | ``` 68 | 69 | 为了完成切换,需要在`shced.h`的PCB定义中增加指向内核栈栈顶指针的域: 70 | 71 | ![1559704125361](lab_5/1559704125361.png) 72 | 73 | 由于PCB的结构定义发生了变化,`sched.h`中的`INIT_TASK`宏也要跟着变化: 74 | 75 | ![1559704364924](lab_5/1559704364924.png) 76 | 77 | 而且需要在`system_call.s`中定义并修改了几个全局变量: 78 | 79 | ![1559704186058](lab_5/1559704186058.png) 80 | 81 | 另外,还得在`sched.c`中增加对全局变量`tss`的定义: 82 | 83 | ![1559704261444](lab_5/1559704261444.png) 84 | 85 | 86 | 87 | # 修改`fork` 88 | 89 | 按照指导书,修改如下: 90 | 91 | ```c 92 | int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, 93 | long ebx,long ecx,long edx, 94 | long fs,long es,long ds, 95 | long eip,long cs,long eflags,long esp,long ss) 96 | { 97 | struct task_struct *p; 98 | int i; 99 | struct file *f; 100 | long *krnstack; 101 | 102 | p = (struct task_struct *) get_free_page(); 103 | if (!p) 104 | return -EAGAIN; 105 | /* 输出新建进程的信息到日志文件中 */ 106 | /* 此时为新建态('N')*/ 107 | fprintk(3, "%ld\t%c\t%ld\n", last_pid, 'N', jiffies); 108 | 109 | /* 子进程内核栈位置 */ 110 | krnstack = (long *)(PAGE_SIZE + (long)p); 111 | /* 设置子进程内核栈 */ 112 | *(--krnstack) = ss & 0xffff; 113 | *(--krnstack) = esp; 114 | *(--krnstack) = eflags; 115 | *(--krnstack) = cs & 0xffff; 116 | *(--krnstack) = eip; 117 | /* first_return_from_kernel中会弹出这些值 */ 118 | *(--krnstack) = ds & 0xffff; 119 | *(--krnstack) = es & 0xffff; 120 | *(--krnstack) = fs & 0xffff; 121 | *(--krnstack) = gs & 0xffff; 122 | *(--krnstack) = esi; 123 | *(--krnstack) = edi; 124 | *(--krnstack) = edx; 125 | /* 当一个新建立的进程被调度执行时在switch_to结束后要执行first_return_from_kernle */ 126 | *(--krnstack) = (long)first_return_from_kernel; 127 | /* switch_to函数中会使用内核栈中的这些值,所以这里需要在最开始的时候先保存下来 */ 128 | *(--krnstack) = ebp; 129 | *(--krnstack) = ecx; 130 | *(--krnstack) = ebx; 131 | *(--krnstack) = 0; // 实际上是eax,子进程fork返回值为0,所以这里设置为0 132 | 133 | task[nr] = p; 134 | *p = *current; /* NOTE! this doesn't copy the supervisor stack */ 135 | 136 | p->kernelstack = krnstack; /* 在PCB中设置好内核栈栈顶 */ 137 | 138 | p->state = TASK_UNINTERRUPTIBLE; 139 | p->pid = last_pid; 140 | p->father = current->pid; 141 | p->counter = p->priority; 142 | p->signal = 0; 143 | p->alarm = 0; 144 | p->leader = 0; /* process leadership doesn't inherit */ 145 | p->utime = p->stime = 0; 146 | p->cutime = p->cstime = 0; 147 | p->start_time = jiffies; 148 | 149 | /* 修改TSS的内容全部注释掉*/ 150 | /* 151 | p->tss.back_link = 0; 152 | p->tss.esp0 = PAGE_SIZE + (long) p; 153 | p->tss.ss0 = 0x10; 154 | p->tss.eip = eip; // 子进程在被调度时直接跳到父进程中同样的位置开始执行 155 | p->tss.eflags = eflags; 156 | p->tss.eax = 0; // 子进程返回0 157 | p->tss.ecx = ecx; 158 | p->tss.edx = edx; 159 | p->tss.ebx = ebx; 160 | p->tss.esp = esp; 161 | p->tss.ebp = ebp; 162 | p->tss.esi = esi; 163 | p->tss.edi = edi; 164 | p->tss.es = es & 0xffff; 165 | p->tss.cs = cs & 0xffff; 166 | p->tss.ss = ss & 0xffff; 167 | p->tss.ds = ds & 0xffff; 168 | p->tss.fs = fs & 0xffff; 169 | p->tss.gs = gs & 0xffff; 170 | p->tss.ldt = _LDT(nr); 171 | p->tss.trace_bitmap = 0x80000000; 172 | if (last_task_used_math == current) 173 | __asm__("clts ; fnsave %0"::"m" (p->tss.i387)); 174 | */ 175 | if (copy_mem(nr,p)) { 176 | task[nr] = NULL; 177 | free_page((long) p); 178 | return -EAGAIN; 179 | } 180 | for (i=0; ifilp[i])) 182 | f->f_count++; 183 | if (current->pwd) 184 | current->pwd->i_count++; 185 | if (current->root) 186 | current->root->i_count++; 187 | if (current->executable) 188 | current->executable->i_count++; 189 | set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); 190 | set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); 191 | p->state = TASK_RUNNING; /* do this last, just in case */ 192 | /* 进程至此进入就绪态('J') */ 193 | fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'J', jiffies); 194 | return last_pid; // 父进程中的返回值 195 | } 196 | ``` 197 | 198 | 注意入栈顺序。 199 | 200 | # 三个问题 201 | 202 | 1. (1). 为什么要加4096? 203 | 204 | 因为`ebx`指向是下个进程的PCB起始地址,而内核栈基地址位于PCB所属那页内存的最高地址,因此加上页大小4096后得到的就是内核栈基地址。 205 | 206 | (2). 为什么没有设置 tss 中的 ss0? 207 | 208 | 因为所有的进程可以共用同一个TSS。 209 | 210 | 2. (1). 子进程第一次执行时, eax= ?为什么要等于这个数?哪里的工作让 eax 等于这样一个数? 211 | 212 | 子进程第一次执行时`eax = 0`,来自于这里:`*(--krnstack) = 0;`。之所以让`eax = 0`,是因为子进程的在`fork`后的返回值需要为`0`才能在代码中区分子进程和父进程,即可以使用这样的代码`if (!fork()) {...}`。 213 | 214 | (2). 这段代码中的 ebx 和 ecx 来自哪里,是什么含义,为什么要通过这些代码将其写到子进程的内核栈中? 215 | 216 | `ebx`和`ecx`来自于父进程。存放的是父进程的用户态`ebx`和`ecx`。这样可以保证切换到子进程用户态运行时有和父进程同样的环境。 217 | 218 | (3). 这段代码中的ebp 来自哪里,是什么含义,为什么要做这样的设置?可以不设置吗?为什么? 219 | 220 | `ebp`来自于父进程,保存的是父进程用户栈基地址指针。即在fork刚刚执行完copy_process的时候,它的用户栈是父进程的用户栈,而非自己的用户栈。当子进程进行其他操作时,造成需要的栈将要与父进程不同了,才会创建自己的用户栈。这么做的好处时当一些子进程什么都不做,系统不用分配额外的空间。这就是Copy On Write。 221 | 222 | 3. 为什么要在切换完 LDT 之后要重新设置 fs=0x17 ?而且为什么重设操作要出现在切换完 LDT 之后,出现在 LDT 之前又会怎么样? 223 | 224 | 切换LDT时,会切换进程使用的用户栈。此时,会利用fs查找它指向的描述符表,并且取出隐藏部分:段基址与段限长放在cs。如果查完表不重新赋值,下次查表的时候就还会查这个表,不能起到切换LDT的作用。放在切完LDT是因为,switch_to之后下一个进程可能会查其他表,不在切完LDT的时候赋值就会影响其他表的查找。 225 | 226 | # TODO 227 | 228 | 在修改后的Linux 0.11 中执行上一个实验的`process.c`时终止了一个子进程之后就进入了死循环,暂时还不知道问题出在哪里。 -------------------------------------------------------------------------------- /lab_5_guide.md: -------------------------------------------------------------------------------- 1 | # 基于内核栈切换的进程切换 2 | 3 | 难度系数:★★★★☆ 4 | 5 | ## 实验目的 6 | 7 | - 深入理解进程和进程切换的概念; 8 | - 综合应用进程、CPU管理、PCB、LDT、内核栈、内核态等知识解决实际问题; 9 | - 开始建立系统认识。 10 | 11 | ## 实验内容 12 | 13 | 现在的Linux 0.11采用TSS(后面会有详细论述)和一条指令就能完成任务切换,虽然简单,但这指令的执行时间却很长,在实现任务切换时大概需要 200 多个时钟周期。而通过堆栈实现任务切换可能要更快,而且采用堆栈的切换还可以使用指令流水的并行优化技术,同时又使得CPU的设计变得简单。所以无论是 Linux还是 Windows,进程/线程的切换都没有使用 Intel 提供的这种TSS切换手段,而都是通过堆栈实现的。 14 | 15 | 本次实践项目就是将Linux 0.11中采用的TSS切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将Linux 0.11中的switch_to实现去掉,写成一段基于堆栈切换的代码。 16 | 17 | 本次实验包括如下内容: 18 | 19 | - 编写汇编程序switch_to: 20 | - 完成主体框架; 21 | - 在主体框架下依次完成PCB切换、内核栈切换、LDT切换等; 22 | - 修改fork(),由于是基于内核栈的切换,所以进程需要创建出能完成内核栈切换的样子。 23 | - 修改PCB,即task_struct结构,增加相应的内容域,同时处理由于修改了task_struct所造成的影响。 24 | - 用修改后的Linux 0.11仍然可以启动、可以正常使用。 25 | - (选做)分析实验4的日志体会修改前后系统运行的差别。 26 | 27 | ## 实验报告 28 | 29 | 回答下面三个问题: 30 | 31 | **1.** 针对下面的代码片段: 32 | 33 | ```assembly 34 | movl tss,%ecx 35 | addl $4096,%ebx 36 | movl %ebx,ESP0(%ecx) 37 | ``` 38 | 39 | 回答问题:(1)为什么要加4096;(2)为什么没有设置tss中的ss0。 40 | 41 | **2.** 针对代码片段: 42 | 43 | ```asm 44 | *(--krnstack) = ebp; 45 | *(--krnstack) = ecx; 46 | *(--krnstack) = ebx; 47 | *(--krnstack) = 0; 48 | ``` 49 | 50 | 回答问题:(1)子进程第一次执行时,eax=?为什么要等于这个数?哪里的工作让eax等于这样一个数?(2)这段代码中的ebx和ecx来自哪里,是什么含义,为什么要通过这些代码将其写到子进程的内核栈中?(3)这段代码中的ebp来自哪里,是什么含义,为什么要做这样的设置?可以不设置吗?为什么? 51 | 52 | **3.** 为什么要在切换完LDT之后要重新设置fs=0x17?而且为什么重设操作要出现在切换完LDT之后,出现在LDT之前又会怎么样? 53 | 54 | ## 评分标准 55 | 56 | - switch_to(system_call.s),40% 57 | - fork.c,30% 58 | - sched.h和sched.c,10% 59 | - 实验报告,20% 60 | 61 | ## 实验提示 62 | 63 | - **TSS切换** 64 | 65 | 在现在的Linux 0.11中,真正完成进程切换是依靠任务状态段(Task State Segment,简称TSS)的切换来完成的。具体的说,在设计“Intel架构”(即x86系统结构)时,每个任务(进程或线程)都对应一个独立的TSS,TSS就是内存中的一个结构体,里面包含了几乎所有的CPU寄存器的映像。有一个任务寄存器(Task Register,简称TR)指向当前进程对应的TSS结构体,所谓的TSS切换就将CPU中几乎所有的寄存器都复制到TR指向的那个TSS结构体中保存起来,同时找到一个目标TSS,即要切换到的下一个进程对应的TSS,将其中存放的寄存器映像“扣在”CPU上,就完成了执行现场的切换,如下图所示。 66 | 67 | ![进程切换](lab_5_guide/wm-1559020714108.png) 68 | 69 | ​ 图1 基于TSS的进程切换 70 | 71 | Intel架构不仅提供了TSS来实现任务切换,而且只要一条指令就能完成这样的切换,即图中的ljmp指令。具体的工作过程是:(1)首先用TR中存取的段选择符在GDT表中找到当前TSS的内存位置,由于TSS是一个段,所以需要用段表中的一个描述符来表示这个段,和在系统启动时论述的内核代码段是一样的,那个段用GDT中的某个表项来描述,还记得是哪项吗?是8对应的第1项。此处的TSS也是用GDT中的某个表项描述,而TR寄存器是用来表示这个段用GDT表中的哪一项来描述,所以TR和CS、DS等寄存器的功能是完全类似的。(2)找到了当前的TSS段(就是一段内存区域)以后,将CPU中的寄存器映像存放到这段内存区域中,即拍了一个快照。(3)存放了当前进程的执行现场以后,接下来要找到目标进程的现场,并将其扣在CPU上,找目标TSS段的方法也是一样的,因为找段都要从一个描述符表中找,描述TSS的描述符放在GDT表中,所以找目标TSS段也要靠GDT表,当然只要给出目标TSS段对应的描述符在GDT表中存放的位置——段选择子就可以了,仔细想想系统启动时那条著名的jmpi 0, 8指令,这个段选择子就放在ljmp的参数中,实际上就jmpi 0, 8中的8。(4)一旦将目标TSS中的全部寄存器映像扣在CPU上,就相当于切换到了目标进程的执行现场了,因为那里有目标进程停下时的CS:EIP,所以此时就开始从目标进程停下时的那个CS:EIP处开始执行,现在目标进程就变成了当前进程,所以TR需要修改为目标TSS段在GDT表中的段描述符所在的位置,因为TR总是指向当前TSS段的段描述符所在的位置。 72 | 73 | 上面给出的这些工作都是一句长跳转指令“ljmp 段选择子:段内偏移”,在段选择子指向的段描述符是TSS段时CPU解释执行的结果,所以基于TSS进行进程/线程切换的switch_to实际上就是一句ljmp指令: 74 | 75 | ```c 76 | #define switch_to(n){ 77 | struct{long a,b;} tmp; 78 | __asm__("movw %%dx,%1" 79 | "ljmp %0" ::"m"(*&tmp.a), "m"(*&tmp.b), "d"(TSS(n)) 80 | } 81 | 82 | #define FIRST_TSS_ENTRY 4 83 | 84 | #define TSS(n)(((unsigned long) n) << 4) + (FIRST_TSS_ENTRY << 3)) 85 | ``` 86 | 87 | GDT表的结构如下图所示,所以第一个TSS表项,即0号进程的TSS表项在第4个位置上,4<<3,即4×8,相当于TSS在GDT表中开始的位置(以字节为单位),TSS(n)找到的是进程n的TSS位置,所以还要再加上n<<4,即n×16,因为每个进程对应有1个TSS和1个LDT,每个描述符的长度都是8个字节,所以是乘以16,其中LDT的作用就是上面论述的那个映射表,关于这个表的详细论述要等到内存管理一章。TSS(n)=n×16+4×8,得到就是进程n(切换到的目标进程)的TSS选择子,将这个值放到dx寄存器中,并且又放置到结构体tmp中32位长整数b的前16位,现在64位tmp中的内容是前32位为空,这个32位数字是段内偏移,就是jmpi 0, 8中的0;接下来的16位是n×16+4×8,这个数字是段选择子,就是jmpi 0, 8中的8,再接下来的16位也为空。所以swith_to的核心实际上就是“ljmp 空, n\*16+4\*8”,现在和前面给出的基于TSS的进程切换联系在一起了。 88 | 89 | ![GDT表中的内容](lab_5_guide/wm-1559020742914.png) 90 | 91 | ​ 图2 GDT表中的内容 92 | 93 | - **本次实验的内容** 94 | 95 | 虽然用一条指令就能完成任务切换,但这指令的执行时间却很长,这条ljmp 指令在实现任务切换时大概需要 200 多个时钟周期。而通过堆栈实现任务切换可能要更快,而且采用堆栈的切换还可以使用指令流水的并行优化技术,同时又使得CPU的设计变得简单。所以无论是 Linux还是 Windows,进程/线程的切换都没有使用 Intel 提供的这种TSS切换手段,而都是通过堆栈实现的。 96 | 97 | 本次实践项目就是将Linux 0.11中采用的TSS切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将Linux 0.11中的switch_to实现去掉,写成一段基于堆栈切换的代码。 98 | 99 | 在现在的Linux 0.11中,真正完成进程切换是依靠任务状态段(Task State Segment,简称TSS)的切换来完成的。具体的说,在设计“Intel架构”(即x86系统结构)时,每个任务(进程或线程)都对应一个独立的TSS,TSS就是内存中的一个结构体,里面包含了几乎所有的CPU寄存器的映像。有一个任务寄存器(Task Register,简称TR)指向当前进程对应的TSS结构体,所谓的TSS切换就将CPU中几乎所有的寄存器都复制到TR指向的那个TSS结构体中保存起来,同时找到一个目标TSS,即要切换到的下一个进程对应的TSS,将其中存放的寄存器映像“扣在”CPU上,就完成了执行现场的切换。 100 | 101 | 要实现基于内核栈的任务切换,主要完成如下三件工作:(1)重写switch_to;(2)将重写的switch_to和schedule()函数接在一起;(3)修改现在的fork()。 102 | 103 | - **schedule与switch_to** 104 | 105 | 目前Linux 0.11中工作的schedule()函数是首先找到下一个进程的数组位置next,而这个next就是GDT中的n,所以这个next是用来找到切换后目标TSS段的段描述符的,一旦获得了这个next值,直接调用上面剖析的那个宏展开switch_to(next);就能完成如图TSS切换所示的切换了。现在,我们不用TSS进行切换,而是采用切换内核栈的方式来完成进程切换,所以在新的switch_to中将用到当前进程的PCB、目标进程的PCB、当前进程的内核栈、目标进程的内核栈等信息。由于Linux 0.11进程的内核栈和该进程的PCB在同一页内存上(一块4KB大小的内存),其中PCB位于这页内存的低地址,栈位于这页内存的高地址;另外,由于当前进程的PCB是用一个全局变量current指向的,所以只要告诉新switch_to()函数一个指向目标进程PCB的指针就可以了。同时还要将next也传递进去,虽然TSS(next)不再需要了,但是LDT(next)仍然是需要的,也就是说,现在每个进程不用有自己的TSS了,因为已经不采用TSS进程切换了,但是每个进程需要有自己的LDT,地址分离地址还是必须要有的,而进程切换必然要涉及到LDT的切换。 106 | 107 | 综上所述,需要将目前的schedule()函数做稍许修改,即将下面的代码 108 | 109 | ```c 110 | if ((*p)->state == TASK\_RUNNING \&\& (*p)->counter > c) c = (*p)->counter, next = i; 111 | ...... 112 | switch_to(next); 113 | ``` 114 | 115 | 修改为 116 | 117 | ``` 118 | if ((*p)->state == TASK\_RUNNING \&\& (*p)->counter > c) c = (*p)->counter, next = i, pnext = *p; 119 | ....... 120 | switch_to(pnext, LDT(next)); 121 | ``` 122 | 123 | - **实现switch_to** 124 | 125 | 这是本次实践项目中最重要的一部分。由于要对内核栈进行精细的操作,所以需要用汇编代码来完成函数switch_to的编写,这个函数依次主要完成如下功能:由于是C语言调用汇编,所以需要首先在汇编中处理栈帧,即处理ebp寄存器;接下来要取出表示下一个进程PCB的参数,并和current做一个比较,如果等于current,则什么也不用做;如果不等于current,就开始进程切换,依次完成PCB的切换、TSS中的内核栈指针的重写、内核栈的切换、LDT的切换以及PC指针(即CS:EIP)的切换。 126 | 127 | ```assembly 128 | switch_to: 129 | pushl %ebp 130 | movl %esp,%ebp 131 | pushl %ecx 132 | pushl %ebx 133 | pushl %eax 134 | movl 8(%ebp),%ebx 135 | cmpl %ebx,current 136 | je 1f 137 | 切换PCB 138 | TSS中的内核栈指针的重写 139 | 切换内核栈 140 | 切换LDT 141 | movl $0x17,%ecx 142 | mov %cx,%fs 143 | cmpl %eax,last_task_used_math //和后面的clts配合来处理协处理器,由于和主题关系不大,此处不做论述 144 | jne 1f 145 | clts 146 | 1: popl %eax 147 | popl %ebx 148 | popl %ecx 149 | popl %ebp 150 | ret 151 | ``` 152 | 153 | 虽然看起来完成了挺多的切换,但实际上每个部分都只有很简单的几条指令。完成PCB的切换可以采用下面两条指令,其中ebx是从参数中取出来的下一个进程的PCB指针, 154 | 155 | ```assembly 156 | movl %ebx,%eax 157 | xchgl %eax,current 158 | ``` 159 | 160 | 经过这两条指令以后,eax指向现在的当前进程,ebx指向下一个进程,全局变量current也指向下一个进程。 161 | 162 | TSS中的内核栈指针的重写可以用下面三条指令完成,其中宏ESP0 = 4,struct tss_struct *tss = &(init_task.task.tss);也是定义了一个全局变量,和current类似,用来指向那一段0号进程的TSS内存。前面已经详细论述过,在中断的时候,要找到内核栈位置,并将用户态下的SS:ESP,CS:EIP以及EFLAGS这五个寄存器压到内核栈中,这是沟通用户栈(用户态)和内核栈(内核态)的关键桥梁,而找到内核栈位置就依靠TR指向的当前TSS。现在虽然不使用TSS进行任务切换了,但是Intel的这态中断处理机制还要保持,所以仍然需要有一个当前TSS,这个TSS就是我们定义的那个全局变量tss,即0号进程的tss,所有进程都共用这个tss,任务切换时不再发生变化。 163 | 164 | ```assembly 165 | movl tss,%ecx 166 | addl $4096,%ebx 167 | movl %ebx,ESP0(%ecx) 168 | ``` 169 | 170 | 定义ESP0 = 4是因为TSS中内核栈指针esp0就放在偏移为4的地方,看一看tss的结构体定义就明白了。 171 | 172 | 完成内核栈的切换也非常简单,和我们前面给出的论述完全一致,将寄存器esp(内核栈使用到当前情况时的栈顶位置)的值保存到当前PCB中,再从下一个PCB中的对应位置上取出保存的内核栈栈顶放入esp寄存器,这样处理完以后,再使用内核栈时使用的就是下一个进程的内核栈了。由于现在的Linux 0.11的PCB定义中没有保存内核栈指针这个域(kernelstack),所以需要加上,而宏KERNEL_STACK就是你加的那个位置,当然将kernelstack域加在task_struct中的哪个位置都可以,但是在某些汇编文件中(主要是在system_call.s中)有些关于操作这个结构一些汇编硬编码,所以一旦增加了kernelstack,这些硬编码需要跟着修改,由于第一个位置,即long state出现的汇编硬编码很多,所以kernelstack千万不要放置在task_struct中的第一个位置,当放在其他位置时,修改system_call.s中的那些硬编码就可以了。 173 | 174 | ```c 175 | KERNEL_STACK = 12 176 | movl %esp,KERNEL_STACK(%eax) 177 | movl 8(%ebp),%ebx //再取一下ebx,因为前面修改过ebx的值 178 | movl KERNEL_STACK(%ebx),%esp 179 | struct task_struct { 180 | long state; 181 | long counter; 182 | long priority; 183 | long kernelstack; 184 | ...... 185 | ``` 186 | 187 | 由于这里将PCB结构体的定义改变了,所以在产生0号进程的PCB初始化时也要跟着一起变化,需要将原来的#define INIT_TASK { 0,15,15, 0,{{},},0,...修改为#define INIT_TASK { 0,15,15,PAGE_SIZE+(long)&init_task, 0,{{},},0,...,即在PCB的第四项中增加关于内核栈栈指针的初始化。 188 | 189 | 再下一个切换就是LDT的切换了,指令movl 12(%ebp),%ecx负责取出对应LDT(next)的那个参数,指令lldt %cx负责修改LDTR寄存器,一旦完成了修改,下一个进程在执行用户态程序时使用的映射表就是自己的LDT表了,地址空间实现了分离。最后一个切换是关于PC的切换,和前面论述的一致,依靠的就是switch_to的最后一句指令ret,虽然简单,但背后发生的事却很多:schedule()函数的最后调用了这个switch_to函数,所以这句指令ret就返回到下一个进程(目标进程)的schedule()函数的末尾,遇到的是},继续ret回到调用的schedule()地方,是在中断处理中调用的,所以回到了中断处理中,就到了中断返回的地址,再调用iret就到了目标进程的用户态程序去执行,和书中论述的内核态线程切换的五段论是完全一致的。这里还有一个地方需要格外注意,那就是switch_to代码中在切换完LDT后的两句,即: 190 | 191 | ```assembly 192 | 切换LDT 193 | movl $0x17,%ecx 194 | mov %cx,%fs 195 | ``` 196 | 197 | 这两句代码的含义是重新取一下段寄存器fs的值,这两句话必须要加、也必须要出现在切换完LDT之后,这是因为在实践项目2中曾经看到过fs的作用——通过fs访问进程的用户态内存,LDT切换完成就意味着切换了分配给进程的用户态内存地址空间,所以前一个fs指向的是上一个进程的用户态内存,而现在需要执行下一个进程的用户态内存,所以就需要用这两条指令来重取fs。不过,细心的读者可能会发现:fs是一个选择子,即fs是一个指向描述符表项的指针,这个描述符才是指向实际的用户态内存的指针,所以上一个进程和下一个进程的fs实际上都是0x17,真正找到不同的用户态内存是因为两个进程查的LDT表不一样,所以这样重置一下fs=0x17有用吗,有什么用? 198 | 199 | 要回答这个问题就需要对段寄存器有更深刻的认识,实际上段寄存器包含两个部分:显式部分和隐式部分,如下图给出实例所示,就是那个著名的jmpi 0, 8,虽然我们的指令是让cs=8,但在执行这条指令时,会在段表(GDT)中找到8对应的那个描述符表项,取出基地址和段限长,除了完成和eip的累加算出PC以外,还会将取出的基地址和段限长放在cs的隐藏部分,即图中的基地址0和段限长7FF。为什么要这样做?下次执行jmp 100时,由于cs没有改过,仍然是8,所以可以不再去查GDT表,而是直接用其隐藏部分中的基地址0和100累加直接得到PC,增加了执行指令的效率。现在想必明白了为什么重新设置fs=0x17了吧?而且为什么要出现在切换完LDT之后? 200 | 201 | ![段寄存器中的两个部分](lab_5_guide/wm-1559020776745.png) 202 | 203 | ​ 图3 段寄存器中的两个部分 204 | 205 | - **修改fork** 206 | 207 | 修改fork()了,和书中论述的原理一致,就是要把进程的用户栈、用户程序和其内核栈通过压在内核栈中的SS:ESP,CS:IP关联在一起。另外,由于fork()这个叉子的含义就是要让父子进程共用同一个代码、数据和堆栈,现在虽然是使用内核栈完成任务切换,但fork()的基本含义不会发生变化。将上面两段描述联立在一起,修改fork()的核心工作就是要形成如下图所示的子进程内核栈结构。 208 | 209 | ![图片描述信息](lab_5_guide/wm.png) 210 | 211 | ​ 图4 fork进程的父子进程结构 212 | 213 | 不难想象,对fork()的修改就是对子进程的内核栈的初始化,在fork()的核心实现copy_process中,p = (struct task_struct *) get_free_page();用来完成申请一页内存作为子进程的PCB,而p指针加上页面大小就是子进程的内核栈位置,所以语句krnstack = (long *) (PAGE_SIZE + (long) p);就可以找到子进程的内核栈位置,接下来就是初始化krnstack中的内容了。 214 | 215 | ```c 216 | *(--krnstack) = ss & 0xffff; 217 | *(--krnstack) = esp; 218 | *(--krnstack) = eflags; 219 | *(--krnstack) = cs & 0xffff; 220 | *(--krnstack) = eip; 221 | ``` 222 | 223 | 这五条语句就完成了上图所示的那个重要的关联,因为其中ss,esp等内容都是copy_proces()函数的参数,这些参数来自调用copy_proces()的进程的内核栈中,就是父进程的内核栈中,所以上面给出的指令不就是将父进程内核栈中的前五个内容拷贝到子进程的内核栈中,图中所示的关联不也就是一个拷贝吗? 224 | 225 | 接下来的工作就需要和switch_to接在一起考虑了,故事从哪里开始呢?回顾一下前面给出来的switch_to,应该从“切换内核栈”完事的那个地方开始,现在到子进程的内核栈开始工作了,接下来做的四次弹栈以及ret处理使用的都是子进程内核栈中的东西, 226 | 227 | ```assembly 228 | 1: popl %eax 229 | popl %ebx 230 | popl %ecx 231 | popl %ebp 232 | ret 233 | ``` 234 | 235 | 为了能够顺利完成这些弹栈工作,子进程的内核栈中应该有这些内容,所以需要对krnstack进行初始化: 236 | 237 | ```c 238 | *(--krnstack) = ebp; 239 | *(--krnstack) = ecx; 240 | *(--krnstack) = ebx; 241 | *(--krnstack) = 0; //这里的0最有意思。 242 | ``` 243 | 244 | 现在到了ret指令了,这条指令要从内核栈中弹出一个32位数作为EIP跳去执行,所以需要弄一个函数地址(仍然是一段汇编程序,所以这个地址是这段汇编程序开始处的标号)并将其初始化到栈中。我们弄的一个名为first_return_from_kernel;的汇编标号,然后可以用语句*(--krnstack) = (long) first_return_from_kernel;将这个地址初始化到子进程的内核栈中,现在执行ret以后就会跳转到first_return_from_kernel去执行了。 245 | 246 | 想一想 first_return_from_kernel要完成什么工作?PCB切换完成、内核栈切换完成、LDT切换完成,接下来应该那个“内核级线程切换五段论”中的最后一段切换了,即完成用户栈和用户代码的切换,依靠的核心指令就是iret,当然在切换之前应该回复一下执行现场,主要就是eax,ebx,ecx,edx,esi,edi,gs,fs,es,ds等寄存器的恢复,下面给出了 first_return_from_kernel的核心代码,当然edx等寄存器的值也应该先初始化到子进程内核栈,即krnstack中。 247 | 248 | ```assembly 249 | popl %edx 250 | popl %edi 251 | popl %esi 252 | pop %gs 253 | pop %fs 254 | pop %es 255 | pop %ds 256 | iret 257 | ``` 258 | 259 | 最后别忘了将存放在PCB中的内核栈指针修改到初始化完成时内核栈的栈顶,即: 260 | 261 | ```c 262 | p->kernelstack = stack; 263 | ``` -------------------------------------------------------------------------------- /lab_6.md: -------------------------------------------------------------------------------- 1 | # 在Ubuntu下编写程序,用信号量解决生产者——消费者问题 2 | 3 | > 编写`pc.c`。 4 | 5 | 强烈推荐看看《APUE》这本书。信号量的使用书中都有介绍。具体实现代码如下: 6 | 7 | ```c 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define BUFSIZE 10 /* 缓冲区大小,按照指导书要求设置为10 */ 16 | 17 | #define CONSUMER_NUM 4 /* 消费者进程数 */ 18 | 19 | #define MAX_NUM 500 20 | 21 | int fd; 22 | sem_t *empty; /* 空槽位的数量 */ 23 | sem_t *full; /* 满槽位的数量 */ 24 | sem_t *mutex; /* 控制对文件互斥的访问 */ 25 | 26 | /** 27 | * 生产者在文件的最后添加数字 28 | */ 29 | void producer() 30 | { 31 | int item_num = 0; 32 | while (item_num < MAX_NUM) 33 | { 34 | sem_wait(empty); 35 | sem_wait(mutex); 36 | if (lseek(fd, 0, SEEK_END) < 0) 37 | fprintf(stderr, "Error in producer lseek\n"); 38 | write(fd, &item_num, sizeof(int)); 39 | fsync(fd); 40 | sem_post(mutex); 41 | sem_post(full); 42 | ++item_num; 43 | } 44 | close(fd); 45 | } 46 | 47 | /** 48 | * 消费者在文件的开头读取数字, 49 | * 并且将读取过的数字删除(通过将后面的数字往前移实现) 50 | */ 51 | void consumer() 52 | { 53 | int item; 54 | int file_len; 55 | int tmp_value; 56 | int j; 57 | 58 | do 59 | { 60 | sem_wait(full); 61 | sem_wait(mutex); 62 | if (lseek(fd, 0, SEEK_SET) < 0) 63 | fprintf(stderr, "Error in consumer lseek\n"); 64 | 65 | if (read(fd, &item, sizeof(int)) == 0) 66 | { 67 | sem_post(mutex); 68 | sem_post(empty); 69 | break; 70 | } 71 | 72 | printf("%d: %d\n", getpid(), item); 73 | 74 | file_len = lseek(fd, 0, SEEK_END); 75 | if (file_len < 0) 76 | fprintf(stderr, "Error when get file length\n"); 77 | /* 通过将后面的数字前移来删除已经读取的数字 */ 78 | /* 这种方式速度特别慢,不知道还有没有别的好办法 */ 79 | for(j = 1; j < (file_len / sizeof(int)); j++) 80 | { 81 | lseek(fd, j * sizeof(int), SEEK_SET); 82 | read(fd, &tmp_value, sizeof(int)); 83 | lseek(fd, (j - 1) * sizeof(int), SEEK_SET); 84 | write(fd, &tmp_value, sizeof(int)); 85 | } 86 | ftruncate(fd, file_len - sizeof(int)); 87 | 88 | sem_post(mutex); 89 | sem_post(empty); 90 | }while(item < MAX_NUM - 1); 91 | 92 | sem_post(full); /* 当第一个进程退出时通知另外一个阻塞在full上的进程,不然另一个进程永远不会退出了 */ 93 | 94 | close(fd); 95 | } 96 | 97 | int main() 98 | { 99 | char empty_name[64]; 100 | char full_name[64]; 101 | char mutex_name[64]; 102 | int i; 103 | pid_t p_pid; /* 生产者进程pid */ 104 | 105 | /* 确保每个信号量有不同的名字 */ 106 | /* from APUE */ 107 | snprintf(empty_name, 64, "/%ld_empty", (long)getpid()); 108 | snprintf(full_name, 64, "/%ld_full", (long)getpid()); 109 | snprintf(mutex_name, 64, "/%ld_mutex", (long)getpid()); 110 | 111 | fd = open("share.file", O_CREAT | O_RDWR | O_TRUNC, 0666); 112 | 113 | empty = sem_open(empty_name, O_CREAT | O_EXCL, S_IRWXU, BUFSIZE); 114 | if (empty == SEM_FAILED) 115 | { 116 | fprintf(stderr, "Error when create empty\n"); 117 | exit(0); 118 | } 119 | full = sem_open(full_name, O_CREAT | O_EXCL, S_IRWXU, 0); 120 | if (full == SEM_FAILED) 121 | { 122 | fprintf(stderr, "Error when create full\n"); 123 | exit(0); 124 | } 125 | mutex = sem_open(mutex_name, O_CREAT | O_EXCL, S_IRWXU, 1); 126 | if (mutex == SEM_FAILED) 127 | { 128 | fprintf(stderr, "Error when create mutex\n"); 129 | exit(0); 130 | } 131 | 132 | printf("Create semphore OK!\n"); 133 | 134 | /* 消费者进程 */ 135 | for (i = 0; i < CONSUMER_NUM; ++i) 136 | { 137 | if (!fork()) 138 | { 139 | consumer(); 140 | exit(0); 141 | } 142 | } 143 | 144 | /* 生产者进程 */ 145 | if (!fork()) 146 | { 147 | producer(); 148 | exit(0); 149 | } 150 | /* 等待所有子进程结束 */ 151 | while (waitpid(-1, NULL, 0) > 0) 152 | ; 153 | 154 | sem_close(empty); 155 | sem_close(full); 156 | sem_close(mutex); 157 | close(fd); 158 | 159 | return 0; 160 | } 161 | ``` 162 | 163 | 注意点在注释中都有体现。 164 | 165 | # 在0.11中实现信号量,用生产者—消费者程序检验之 166 | 167 | 按照[实验二](./lab_2.md)中的步骤添加四个系统调用: 168 | 169 | ```c 170 | sem_t* sem_open(const char *name, unsigned int value); 171 | int sem_wait(semt_t *sem); 172 | int sem_post(sem_t *sem); 173 | int sem_unlink(const char *name); 174 | ``` 175 | 176 | 先在`include/`文件夹中添加`sem.h`头文件,内容如下: 177 | 178 | ![1561698914664](lab_6/1561698914664.png) 179 | 180 | 然后在`kernel/`文件夹中添加`sem.c`,内容如下: 181 | 182 | ```c 183 | #include 184 | #include /* strcpy() strcmp() */ 185 | #include /* get_fs_byte() */ 186 | #include /* NULL */ 187 | #include /* cli() sti() */ 188 | #include /* printk() */ 189 | 190 | #define SEMS_SIZE 5 191 | 192 | static sem_t sems[SEMS_SIZE] = { 193 | {"", 0, NULL}, 194 | {"", 0, NULL}, 195 | {"", 0, NULL}, 196 | {"", 0, NULL}, 197 | {"", 0, NULL}, 198 | }; 199 | 200 | sem_t* sys_sem_open(const char *name, unsigned int value) 201 | { 202 | if (name == NULL) 203 | { 204 | printk("name == NULL\n"); 205 | return NULL; 206 | } 207 | int i, index = -1; 208 | char temp_name[MAX_NAME]; 209 | for (i = 0; i < MAX_NAME; ++i) 210 | { 211 | temp_name[i] = get_fs_byte(name+i); 212 | if (temp_name[i] == '\0') 213 | break; 214 | } 215 | if (i == 0 || i == MAX_NAME) 216 | { 217 | printk("name too long or too short, i = %d\n", i); 218 | return NULL; 219 | } 220 | 221 | for (i = 0; i < SEMS_SIZE; ++i) 222 | { 223 | if (strcmp(sems[i].name, "") == 0) 224 | { 225 | index = index == -1 ? i : index; 226 | continue; 227 | } 228 | if (strcmp(sems[i].name, temp_name) == 0) 229 | return &sems[i]; 230 | } 231 | sem_t *res = NULL; 232 | if (index != -1) 233 | { 234 | res = &sems[index]; 235 | // printk("before set: name is %s\n", res->name); 236 | // strcpy(sems[index].name, temp_name); // 不能使用strcpy,是因为在内核态的原因吗? 237 | for (i = 0; temp_name[i] != '\0'; ++i) 238 | sems[index].name[i] = temp_name[i]; 239 | sems[index].name[i] = '\0'; 240 | // printk("after set: name is %s\n", res->name); 241 | res->value = value; 242 | } 243 | else 244 | printk("no empty slots: index = %d\n", index); 245 | return res; 246 | } 247 | 248 | int sys_sem_wait(sem_t *sem) 249 | { 250 | if (sem == NULL || sem < sems || sem >= sems + SEMS_SIZE) 251 | return -1; 252 | cli(); 253 | while (sem->value == 0) 254 | sleep_on(&sem->wait_queue); 255 | sem->value--; 256 | sti(); 257 | return 0; 258 | } 259 | 260 | int sys_sem_post(sem_t *sem) 261 | { 262 | if (sem == NULL || sem < sems || sem >= sems + SEMS_SIZE) 263 | return -1; 264 | cli(); 265 | wake_up(&sem->wait_queue); 266 | sem->value++; 267 | sti(); 268 | return 0; 269 | } 270 | 271 | int sys_sem_unlink(const char *name) 272 | { 273 | if (name == NULL) 274 | return -1; 275 | int i; 276 | char temp_name[MAX_NAME]; 277 | for (i = 0; i < MAX_NAME; ++i) 278 | { 279 | temp_name[i] = get_fs_byte(name+i); 280 | if (temp_name[i] == '\0') 281 | break; 282 | } 283 | if (i == 0 || i == MAX_NAME) 284 | return -1; 285 | temp_name[i] = '\0'; 286 | 287 | for (i = 0; i < SEMS_SIZE; ++i) 288 | { 289 | if (strcmp(sems[i].name, temp_name)) 290 | { 291 | sems[i].name[0] = '\0'; 292 | sems[i].value = 0; 293 | sems[i].wait_queue = NULL; 294 | return 0; 295 | } 296 | } 297 | 298 | return -1; 299 | } 300 | ``` 301 | 302 | **注意,由于在`sys.h`中使用了`sem_t`这个类型,需要`#include `。** 303 | 304 | # 用信号量解决生产者—消费者问题 305 | 306 | 上面的`pc.c`基本上不用做太大的改动即可用于Linux 0.11,注意,Linux 0.11中没有`ftruncate`、`fsync`和`snprintf`,后面两个都有可以凑合用的替代品,但是没有`ftruncate`的替代品,因此,通过修改`fcntl`实现了一个`ftruncate`(此处参考实验楼的[这里](https://www.shiyanlou.com/courses/reports/373603/))。先修改`fcntl.h`,增加一个宏定义: 307 | 308 | ![1561699245393](lab_6/1561699245393.png) 309 | 310 | 然后修改`fcntl.c`如下: 311 | 312 | ![1561699326325](lab_6/1561699326325.png) 313 | 314 | 然后重新编译内核即可。 315 | 316 | 最后在Linux 0.11中的`pc.c`如下: 317 | 318 | ```c 319 | #define __LIBRARY__ 320 | 321 | #include 322 | #include 323 | #include 324 | #include 325 | #include 326 | #include 327 | 328 | _syscall2(sem_t*, sem_open, const char*, name, unsigned int, value); 329 | _syscall1(int, sem_wait, sem_t*, sem); 330 | _syscall1(int, sem_post, sem_t*, sem); 331 | _syscall1(int, sem_unlink, const char*, name); 332 | 333 | #define BUFSIZE 10 334 | 335 | #define CONSUMER_NUM 4 336 | 337 | #define MAX_NUM 500 338 | 339 | int fd; 340 | sem_t *empty; 341 | sem_t *full; 342 | sem_t *mutex; 343 | 344 | int ftruncate(int fd, unsigned long size) 345 | { 346 | return fcntl(fd, F_CHGSIZE, size); 347 | } 348 | 349 | void producer() 350 | { 351 | int item_num = 0; 352 | while (item_num < MAX_NUM) 353 | { 354 | sem_wait(empty); 355 | sem_wait(mutex); 356 | if (lseek(fd, 0, SEEK_END) < 0) 357 | fprintf(stderr, "Error in producer lseek\n"); 358 | write(fd, &item_num, sizeof(int)); 359 | sync(); 360 | sem_post(mutex); 361 | sem_post(full); 362 | ++item_num; 363 | } 364 | close(fd); 365 | } 366 | 367 | void consumer() 368 | { 369 | int item; 370 | int file_len; 371 | int tmp_value; 372 | int j; 373 | 374 | do 375 | { 376 | sem_wait(full); 377 | sem_wait(mutex); 378 | if (lseek(fd, 0, SEEK_SET) < 0) 379 | fprintf(stderr, "Error in consumer lseek\n"); 380 | 381 | if (read(fd, &item, sizeof(int)) == 0) 382 | { 383 | sem_post(mutex); 384 | sem_post(empty); 385 | break; 386 | } 387 | 388 | printf("%d: %d\n", getpid(), item); 389 | fflush(stdout); 390 | 391 | file_len = lseek(fd, 0, SEEK_END); 392 | if (file_len < 0) 393 | fprintf(stderr, "Error when get file length\n"); 394 | for(j = 1; j < (file_len / sizeof(int)); j++) 395 | { 396 | lseek(fd, j * sizeof(int), SEEK_SET); 397 | read(fd, &tmp_value, sizeof(int)); 398 | lseek(fd, (j - 1) * sizeof(int), SEEK_SET); 399 | write(fd, &tmp_value, sizeof(int)); 400 | } 401 | ftruncate(fd, file_len - sizeof(int)); 402 | 403 | sem_post(mutex); 404 | sem_post(empty); 405 | }while(item < MAX_NUM - 1); 406 | sem_post(full); 407 | close(fd); 408 | } 409 | 410 | int main() 411 | { 412 | char empty_name[20]; 413 | char full_name[20]; 414 | char mutex_name[20]; 415 | int i; 416 | pid_t p_pid; 417 | 418 | /* from APUE */ 419 | sprintf(empty_name, "/%ld_empty", (long)getpid()); 420 | sprintf(full_name, "/%ld_full", (long)getpid()); 421 | sprintf(mutex_name, "/%ld_mutex", (long)getpid()); 422 | 423 | fd = open("share.file", O_CREAT | O_RDWR | O_TRUNC, 0666); 424 | 425 | empty = sem_open(empty_name, BUFSIZE); 426 | if (empty == NULL) 427 | { 428 | fprintf(stderr, "Error when create empty\n"); 429 | exit(0); 430 | } 431 | full = sem_open(full_name, 0); 432 | if (full == NULL) 433 | { 434 | fprintf(stderr, "Error when create full\n"); 435 | exit(0); 436 | } 437 | mutex = sem_open(mutex_name, 1); 438 | if (mutex == NULL) 439 | { 440 | fprintf(stderr, "Error when create mutex\n"); 441 | exit(0); 442 | } 443 | 444 | printf("Create semphore OK!\n"); 445 | 446 | for (i = 0; i < CONSUMER_NUM; ++i) 447 | { 448 | if (!fork()) 449 | { 450 | consumer(); 451 | exit(0); 452 | } 453 | } 454 | 455 | if (!fork()) 456 | { 457 | producer(); 458 | exit(0); 459 | } 460 | while (waitpid(-1, NULL, 0) > 0) 461 | ; 462 | 463 | sem_unlink(empty_name); 464 | sem_unlink(full_name); 465 | sem_unlink(mutex_name); 466 | close(fd); 467 | 468 | return 0; 469 | } 470 | ``` 471 | 472 | ~~但是在这个文件中存在一个BUG?执行`pc`时会产生大量乱七八糟的输出,Bochs直接就死机了。。也不知道为啥,欢迎知道的同学告诉我哈。。(已经解决,是之前的信号量实现问题。)~~ 473 | 474 | 将实验结果输出到文件中,执行以下命令: 475 | 476 | ![深度截图_选择区域_20190729130246](lab_6/深度截图_选择区域_20190729130246.png) 477 | 478 | 等待执行完毕后打开out.txt内容如下: 479 | 480 | ![深度截图_选择区域_20190729130400](lab_6/深度截图_选择区域_20190729130400.png) 481 | 482 | ![深度截图_选择区域_20190729130437](lab_6/深度截图_选择区域_20190729130437.png) 483 | 484 | 试验完成。 485 | 486 | # 实验问答 487 | 488 | > 在pc.c中去掉所有与信号量有关的代码,再运行程序,执行效果有变化吗?为什么会这样? 489 | 490 | 很明显,去掉进程同步结构,会造成输出混乱。因为无法保证按顺序取数据,写数据时机也不能保证。 491 | 492 | > 实验的设计者在第一次编写生产者——消费者程序的时候,是这么做的......可行吗? 493 | 494 | 显然不可行,有造成死锁的可能。不能先对Mutex加锁再对Empty加锁。 -------------------------------------------------------------------------------- /lab_7.md: -------------------------------------------------------------------------------- 1 | # 跟踪Linux 0.11地址翻译过程 2 | 3 | > 用Bochs调试工具跟踪Linux 0.11的地址翻译(地址映射)过程,了解IA-32和Linux 0.11的内存管理机制。 4 | > 5 | > 以汇编级调试的方式启动bochs,引导Linux 0.11,在0.11下编译和运行test.c。它是一个无限循环的程序,永远不会主动退出。然后在调试器中通过查看各项系统参数,从逻辑地址、LDT表、GDT表、线性地址到页表,计算出变量i的物理地址。最后通过直接修改物理内存的方式让test.c退出运行。 6 | 7 | 首先看看`test.c`的代码: 8 | 9 | ```c 10 | #include 11 | int i = 0x12345678; 12 | int main(void) 13 | { 14 | printf("The logical/virtual address of i is 0x%08x", &i); 15 | fflush(stdout); 16 | while (i) 17 | ; 18 | return 0; 19 | } 20 | ``` 21 | 22 | 在Linux 0.11下跑跑看: 23 | 24 | ![2019-07-11 13-07-36屏幕截图](lab_7/2019-07-11 13-07-36屏幕截图.png) 25 | 26 | 打印出了`i`这个变量的逻辑地址,然后就陷入死循环了。接下来我们要做的就是通过Bochs调试器修改`i`的值,让循环能够退出,也就是将`i`修改为0。 27 | 28 | ## 地址翻译过程 29 | 30 | 由于Bochs中我们看到的都是物理地址,我们需要通过`i`的逻辑地址计算出物理地址。我们先来总结一下通过逻辑地址找到真实物理地址的过程: 31 | 32 | 由于分段机制的存在,我们看到的这个逻辑地址(`0x00003004`)只是段内偏移而已,要找到真实的虚拟地址,我们还需要根据段寄存器找到段基址。 33 | 34 | **具体的操作过程如下:** 35 | 36 | 1. `ds`、`fs`等段寄存器中保存的是段基址在LDT(局部描述符表)中的偏移,也就是说LDT[ds]就是真正的段基址,那么问题就是如何找到LDT的基址? 37 | 2. `ldtr`寄存器保存的是当前进程的LDT基址在GDT(全局描述符表)中的偏移,也就是说GDT[ldtr]中就是LDT的基址,那么如何找到GDT的基址呢?`gdtr`寄存器中保存的就是GDT的基址。 38 | 39 | 综上说述,用一种比较好理解的方式来说就是 真正的虚拟地址=gdtr\[ldtr\]\[ds\] + 段内偏移。接下来就实际操作一下来找到`i`的真正虚拟地址。 40 | 41 | ## 计算虚拟地址 42 | 43 | 首先按照实验指导书上写的一通操作将Bochs停在`while`循环里面: 44 | 45 | ![深度截图_选择区域_20190711132339](lab_7/深度截图_选择区域_20190711132339.png) 46 | 47 | 看到,“刚好”停在判断语句上。另外我们还注意到用到的段基址寄存器是`ds`。接下来我们用`sreg`命令查看一下各个寄存器的值: 48 | 49 | ![深度截图_选择区域_20190711132557](lab_7/深度截图_选择区域_20190711132557.png) 50 | 51 | 观察一下我们所关心的几个寄存器的值:`ds`寄存器的值是`0x0017`,`ldtr`的值是`0x0068`,`gdtr`的值是`0x00005cb8`。也就是说GDT表的基址是`0x00005cb8`,而LDT表的基址放在GDT表中的偏移为`0x0068=0000000001101000`(二进制)。由于段选择子(我们提到的段寄存器包括`ds`、`ldtr`都是段选择子)有自己的特定格式,这不是真正的偏移量,那么真正的偏移是多少呢?接下来就要提到段选择子的格式了: 52 | 53 | ![img](lab_7/10191360-fd5c58534c5db030.png) 54 | 55 | 只有前面的13位才是真正的索引号,也就是偏移量。RPL是请求特权级,当访问一个段时,处理器要检查RPL和CPL(放在cs的位0和位1中,用来表示当前代码的特权级),即使程序有足够的特权级(CPL)来访问一个段,但如果RPL(如放在ds中,表示请求数据段)的特权级不足,则仍然不能访问,即如果RPL的数值大于CPL(数值越大,权限越小),则用RPL的值覆盖CPL的值。而段选择子中的TI是表指示标记,如果TI=0,则表示段描述符(段的详细信息)在GDT(全局描述符表)中,即去GDT中去查;而TI=1,则去LDT(局部描述符表)中去查。**那么真正的偏移量就应该是1101(二进制)=13(十进制)**。也就是说LDT的基址是GDT表中的第14项(下标从0开始)。 56 | 57 | 我们已经知道了GDT表的基址是`0x00005cb8`,LDT表的基址在GDT表中,偏移量为13。接下来查看一下下标为13处的值:使用`xp /2w 0x00005cb8+13*8`查看以8字节单位,下标为13处的值: 58 | 59 | ![深度截图_选择区域_20190712125327](lab_7/深度截图_选择区域_20190712125327.png) 60 | 61 | 可以看到,确实和`ldtr`所在行中`dl`和`dh`的值相同。这就是LDT的物理地址,但是我们还需要根据特定的格式将这两个数字组合起来。接下来我们看看段描述符的格式: 62 | 63 | ![img](lab_7/10191360-6c638b96561fdb65.png) 64 | 65 | 这里,低32位就是`dl=0xc2d00068`,高32位就是`dh=0x000082f9`。根据描述符的格式,将段基址组合起来得到`0x00f9c2d0`。这就是LDT的真正物理地址了。 66 | 67 | 有了LDT的基址,又有了`ds=0x0017`这个偏移,接下来就能得到`i`的真正虚拟地址了。`ds`是个段选择子,根据段选择子的格式,我们可以算出`ds`对应的偏移为`0x0017=0000000000010111`,其中RPL=11,索引为10(二进制)=2(十进制),表示查找的是LDT中第三个段描述符(从0开始编号)。接下来查看一下LDT中的第三项: 68 | 69 | ![深度截图_选择区域_20190712130623](lab_7/深度截图_选择区域_20190712130623.png) 70 | 71 | 结果是`0x00003fff`和`0x10c0f300`。同样,我们按照上面的段描述符的格式将其组合起来得到`0x10000000`,这就是`i`的段基址了。**将它和段偏移`0x3004`组合起来得到`0x10003004`,这就是`i`的真正虚拟地址了。**用`calc ds:0x3004`可以验证这个结果: 72 | 73 | ![深度截图_选择区域_20190712130955](lab_7/深度截图_选择区域_20190712130955.png) 74 | 75 | ## 计算物理地址 76 | 77 | 有了虚拟地址,接下来就是查询页表得到真正的物理地址了。在Linux 0.11中采用的是一个二级页表的结构。将线性地址分为页目录号、页表号和页内偏移,图示如下: 78 | 79 | ![深度截图_选择区域_20190712131304](lab_7/深度截图_选择区域_20190712131304.png) 80 | 81 | 它们分别对应了32位线性地址的10位+10位+12位。所以,虚拟地址`0x10003004`对应的页目录号是64,页表号为3,页内偏移是4。 82 | 83 | 在IA-32下,页目录表的基址有`CR3`寄存器给出,`creg`命令能够查看: 84 | 85 | ![深度截图_选择区域_20190712131514](lab_7/深度截图_选择区域_20190712131514.png) 86 | 87 | `CR3=0x00000000`,说明页目录表的基址为0。页目录表中以4字节为单位,查看页目录表中下标为64的位置处的值: 88 | 89 | ![深度截图_选择区域_20190712131741](lab_7/深度截图_选择区域_20190712131741.png) 90 | 91 | 看到值为`0x00faa027`。页表的基址就隐藏在这4字节的数字中。这32位中前20位为物理地址,后面是一些属性信息。因此,`0x00faa027`所代表的物理地址就是`0x00faa`,也即页表的基地址为`0x00faa000`。接下来从这个位置查找页表中下标为3的页表项: 92 | 93 | ![深度截图_选择区域_20190712132135](lab_7/深度截图_选择区域_20190712132135.png) 94 | 95 | 说明真实的页框的基址就是`0x00fa7`,后面的是一些属性信息。我们把页框的基址和业内偏移`0x004`组合到一起,**得到`0x00fa7004`,这就是`i`的物理地址了。** 96 | 97 | 通过指导书上的命令来验证: 98 | 99 | ![深度截图_选择区域_20190712132422](lab_7/深度截图_选择区域_20190712132422.png) 100 | 101 | 完全正确! 102 | 103 | 接下来通过直接修改内存来改变`i`的值,使其值为0来退出循环。命令是`setpmem 0x00fa4004 4 0`,表示从0x00fa7004地址开始的4个字节都设为0。然后再用“c”命令继续Bochs的运行,可以看到test退出了,说明i的修改成功了,此项实验结束。 104 | 105 | # 基于共享内存的生产者消费者程序 106 | 107 | 主要是用`shmget()`和`shmat()`这几个函数,使用方法可以查阅`APUE`或者百度。 108 | 109 | 生产者进程如下: 110 | 111 | ```c 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | 119 | #define BUFSIZE 10 120 | #define MAX_NUM 500 121 | 122 | // producer 123 | int main() 124 | { 125 | sem_t *empty; 126 | sem_t *full; 127 | sem_t *mutex; 128 | int shmid; 129 | int *p; 130 | int i; 131 | 132 | sem_unlink("empty"); 133 | sem_unlink("full"); 134 | sem_unlink("mutex"); 135 | 136 | empty = sem_open("empty", O_CREAT | O_EXCL, S_IRWXU, BUFSIZE); 137 | if (empty == SEM_FAILED) 138 | { 139 | fprintf(stderr, "Error when create semaphore empty\n"); 140 | exit(1); 141 | } 142 | 143 | full = sem_open("full", O_CREAT | O_EXCL, S_IRWXU, 0); 144 | if (full == SEM_FAILED) 145 | { 146 | fprintf(stderr, "Error when create semaphore full\n"); 147 | exit(1); 148 | } 149 | mutex = sem_open("mutex", O_CREAT | O_EXCL, S_IRWXU, 1); 150 | if (mutex == SEM_FAILED) 151 | { 152 | fprintf(stderr, "Error when create semaphore mutex\n"); 153 | exit(1); 154 | } 155 | printf("Create semphore OK!\n"); 156 | 157 | if((shmid = shmget(1024, MAX_NUM * sizeof(int), IPC_CREAT | 0777)) < 0) 158 | { 159 | fprintf(stderr, "shmget error\n"); 160 | exit(1); 161 | } 162 | 163 | if((p = (int *)shmat(shmid, NULL, SHM_EXEC)) == (void *)-1) 164 | { 165 | fprintf(stderr, "shmat error\n"); 166 | exit(1); 167 | } 168 | 169 | for (i = 0; i < MAX_NUM; ++i) 170 | { 171 | sem_wait(empty); 172 | sem_wait(mutex); 173 | *(p + i % BUFSIZE) = i; 174 | printf("add %d to buffer\n", i); 175 | sem_post(mutex); 176 | sem_post(full); 177 | } 178 | 179 | sem_unlink("empty"); 180 | sem_unlink("full"); 181 | sem_unlink("mutex"); 182 | 183 | return 0; 184 | } 185 | 186 | ``` 187 | 188 | 消费者进程如下: 189 | 190 | ```c 191 | #include 192 | #include 193 | #include 194 | #include 195 | #include 196 | 197 | #define BUFSIZE 10 198 | 199 | #define MAX_NUM 500 200 | 201 | sem_t* Sem_open(const char *name, unsigned int value) 202 | { 203 | sem_t *sem = sem_open(name, value); 204 | if (sem == SEM_FAILED) 205 | { 206 | fprintf(stderr, "Error when create semaphore %s\n", name); 207 | exit(1); 208 | } 209 | return sem; 210 | } 211 | 212 | int main() 213 | { 214 | int i; 215 | int shmid; 216 | sem_t *empty; 217 | sem_t *full; 218 | sem_t *mutex; 219 | int *p; 220 | int data; 221 | 222 | empty = Sem_open("empty", 0); // 使用现有信号量只需指定名字和flag参数的0值 223 | full = Sem_open("full", 0); 224 | mutex = Sem_open("mutex", 0); 225 | 226 | printf("Semaphore Open Sucess!\n"); 227 | fflush(stdout); 228 | 229 | shmid = shmget(1024, (BUFSIZE) * sizeof(int), IPC_CREAT | 0777); 230 | 231 | if (shmid == -1) 232 | { 233 | fprintf(stderr, "shmget Error!\n"); 234 | exit(1); 235 | } 236 | 237 | p = (int *)shmat(shmid, NULL, SHM_EXEC); 238 | for (i = 0; i < MAX_NUM; ++i) 239 | { 240 | sem_wait(full); 241 | sem_wait(mutex); 242 | data = *(p + i % BUFSIZE); 243 | printf("%d: %d\n", getpid(), data); 244 | fflush(stdout); 245 | sem_post(mutex); 246 | sem_post(empty); 247 | } 248 | return 0; 249 | } 250 | 251 | ``` 252 | 253 | **特别提醒:没有父子关系的进程之间进行共享内存,shmget()的第一个参数key不要用IPC_PRIVATE,否则无法共享。用什么数字可视心情而定,只要确保两个进程用的是同一个值即可。** 254 | 255 | # 在Linux0.11下实现共享内存 256 | 257 | 在整个实现过程中存在着两个问题需要解决。 258 | 259 | 首先,要获得物理地址非常容易,直接调用`get_free_page()`即可。但是如何在一个进程中获取一个空闲的虚拟地址,然后在页表中将虚拟地址和物理地址联系起来呢?实验指导书中提到,使用`put_page()`可以将建立虚拟地址和物理地址的映射,那么就剩下如何获得一个虚拟地址了。仔细查看注释中的图13-6,进程的PCB中保存着进程所占用的所有内存空间的信息,比如代码段起始位置,代码段结束位置,栈开始位置等等。为了获得一个虚拟地址,我们可以想想当调用`malloc()`动态分配内存空间时是如何获取虚拟地址的。我们会发现,`malloc()`获得的虚拟地址是通过递增`brk`来分配一个虚拟地址的。也就是说,`brk`维持了现在地址空间底端已经使用的虚拟地址,我们也只需要递增`brk`即可获得一块空闲的虚拟地址。另外需要注意的是,图中显示的那些地址比如`start_cdoe`、`end_code`等等都是段内偏移,因此需要先通过`get_base()`获取段基址,加上段基址后才是真正的虚拟地址。 260 | 261 | 第二个问题我们先看看实现共享内存的代码: 262 | 263 | `shm.h`如下: 264 | 265 | ```c 266 | #ifndef _SHM_H_ 267 | #define _SHM_H_ 268 | 269 | #define SHM_SIZE 20 270 | 271 | typedef struct 272 | { 273 | unsigned int key; 274 | unsigned int size; 275 | unsigned long page; // address 276 | }shm_t; 277 | 278 | #endif 279 | ``` 280 | 281 | `shm.c`如下: 282 | 283 | ```c 284 | #include 285 | #include 286 | #include 287 | #include 288 | #include 289 | #include 290 | 291 | static shm_t shm_list[SHM_SIZE] = {0}; 292 | 293 | int sys_shmget(unsigned int key, size_t size) 294 | { 295 | int i; 296 | void *page; 297 | if (size > PAGE_SIZE) 298 | return -EINVAL; 299 | page = get_free_page(); // get an empty physic page. 300 | // printk("get free page in %p\n", page); 301 | if (!page) 302 | return -ENOMEM; 303 | for (i = 0; i < SHM_SIZE; ++i) 304 | if (shm_list[i].key == key) 305 | return i; 306 | // printk("create new share memory space.\n"); 307 | for (i = 0; i < SHM_SIZE; ++i) 308 | { 309 | if (shm_list[i].key == 0) // find an empty slot. 310 | { 311 | shm_list[i].key = key; 312 | shm_list[i].page = page; 313 | shm_list[i].size = size; 314 | return i; 315 | } 316 | } 317 | // printk("no empty slot\n"); 318 | return -1; 319 | } 320 | 321 | void* sys_shmat(int shmid) 322 | { 323 | int i; 324 | void *addr; 325 | if (0 < shmid || shmid > SHM_SIZE) 326 | return -EINVAL; 327 | addr = current->brk + get_base(current->ldt[1]); // we can use this virtual address 328 | current->brk += PAGE_SIZE; 329 | // printk("get an empty virtual address in %p\n", addr); 330 | if (shm_list[shmid].key != 0) 331 | { 332 | put_page(shm_list[shmid].page, addr); 333 | incr_mem_map(shm_list[shmid].page); // 重点 334 | return current->brk - PAGE_SIZE; 335 | } 336 | // printk("this share memory is not exits.\n"); 337 | 338 | return -EINVAL; 339 | } 340 | ``` 341 | 342 | 实现很简单,但是里面还存在一个问题。我们现在的生产消费者程序有两个进程,一个`producer`,一个`consumer`。他们之间共享一块物理内存。当`producer`退出时,操作系统会自动调用`free_page()`来回收这个进程使用的所有内存页,那么也会回收这块共享内存。所谓回收也就是在全局的`mem_map`表中将该物理内存对应的位置的值减一,如果为0了就真正的回收内存,标记为可用。当我们调用`get_free_page()`时只是在`mem_map`中将对应物理内存设置为1,那么`producer`退出时会减小至0,该内存已经被标记为可用了。然后,`consumer`退出,操作系统同样会尝试取释放这块共享内存,但是这块内存已经被释放了,因此会出现`trying to free free page`,接着就会宕机。因此,当我们调用`shmat`时我们需要在`mem_map`中将对应项加一,这样可以避免宕机。但是可能会出现内存泄漏? 343 | 344 | 因此,我们需要在`memory.c`中增加一个对`mem_map`进行加一操作的函数,如下: 345 | 346 | ```c 347 | void incr_mem_map(unsigned long addr) 348 | { 349 | 350 | mem_map[MAP_NR(addr)]++; 351 | } 352 | ``` 353 | 354 | 具体为什么这么实现可以看看《注释》。 355 | 356 | 接下来就是修改后的`consumer.c`和`producer.c`了,实现如下: 357 | 358 | `producer.c` 359 | 360 | ```c 361 | #define __LIBRARY__ 362 | 363 | #include 364 | #include 365 | #include 366 | #include 367 | #include 368 | #include 369 | 370 | /* for semphare */ 371 | _syscall2(sem_t*, sem_open, const char*, name, unsigned int, value); 372 | _syscall1(int, sem_wait, sem_t*, sem); 373 | _syscall1(int, sem_post, sem_t*, sem); 374 | _syscall1(int, sem_unlink, const char*, name); 375 | 376 | _syscall1(void*, shmat, int, shmid); 377 | _syscall2(int, shmget, unsigned int, key, size_t, size); 378 | 379 | #define BUFSIZE 10 380 | #define MAX_NUM 500 381 | 382 | /* producer */ 383 | int main() 384 | { 385 | sem_t *empty; 386 | sem_t *full; 387 | sem_t *mutex; 388 | int shmid; 389 | int *p; 390 | int i; 391 | 392 | sem_unlink("empty"); 393 | sem_unlink("full"); 394 | sem_unlink("mutex"); 395 | 396 | empty = sem_open("empty", BUFSIZE); 397 | full = sem_open("full", 0); 398 | mutex = sem_open("mutex", 1); 399 | 400 | shmget(1024, MAX_NUM * sizeof(int)); 401 | p = (int *)shmat(shmid); 402 | 403 | for (i = 0; i < MAX_NUM; ++i) 404 | { 405 | sem_wait(empty); 406 | sem_wait(mutex); 407 | *(p + i % BUFSIZE) = i; 408 | printf("add %d to buffer\n", i); 409 | fflush(stdout); 410 | sem_post(mutex); 411 | sem_post(full); 412 | } 413 | 414 | /* sem_unlink("empty"); 415 | sem_unlink("full"); 416 | sem_unlink("mutex"); */ 417 | printf("Producer exit\n"); 418 | fflush(stdout); 419 | 420 | return 0; 421 | } 422 | ``` 423 | 424 | `consumer.c` 425 | 426 | ```c 427 | #define __LIBRARY__ 428 | 429 | #include 430 | #include 431 | #include 432 | #include 433 | #include 434 | 435 | _syscall2(sem_t*, sem_open, const char*, name, unsigned int, value); 436 | _syscall1(int, sem_wait, sem_t*, sem); 437 | _syscall1(int, sem_post, sem_t*, sem); 438 | _syscall1(int, sem_unlink, const char*, name); 439 | 440 | _syscall1(void*, shmat, int, shmid); 441 | _syscall2(int, shmget, unsigned int, key, size_t, size); 442 | 443 | 444 | #define BUFSIZE 10 445 | 446 | #define MAX_NUM 500 447 | 448 | sem_t* Sem_open(const char *name, unsigned int value) 449 | { 450 | sem_t *sem = sem_open(name, value); 451 | if (sem == NULL) 452 | { 453 | fprintf(stderr, "Error when create semaphore %s\n", name); 454 | exit(1); 455 | } 456 | return sem; 457 | } 458 | 459 | int main() 460 | { 461 | int i; 462 | int shmid; 463 | sem_t *empty; 464 | sem_t *full; 465 | sem_t *mutex; 466 | int *p; 467 | int data; 468 | 469 | empty = Sem_open("empty", BUFSIZE); 470 | full = Sem_open("full", 0); 471 | mutex = Sem_open("mutex", 1); 472 | 473 | shmid = shmget(1024, (BUFSIZE) * sizeof(int)); 474 | 475 | p = (int *)shmat(shmid); 476 | 477 | for (i = 0; i < MAX_NUM; ++i) 478 | { 479 | sem_wait(full); 480 | sem_wait(mutex); 481 | data = *(p + i % BUFSIZE); 482 | printf("%d: %d\n", getpid(), data); 483 | fflush(stdout); 484 | sem_post(mutex); 485 | sem_post(empty); 486 | } 487 | return 0; 488 | } 489 | 490 | ``` 491 | 492 | 也没有太大的改动。 493 | 494 | 另外还有一个注意点,我的这两个程序中没有对信号量进行释放,因为如果在`producer`中释放了在`consumer`中就没法访问了。当然,你可以在`consumer`中释放,但是我觉得这样并不优雅。。所以就没这样做。如果想做到释放,那么可以在信号量的实现中增加计数器,如果计数值为0了才真正释放该信号量,这样就可以比较优雅的在生产消费者程序中释放信号量了。在此我没有实现。 495 | 496 | --------------------------------------------------------------------------------