commit 5f1b6a7b0629a4c39a3bf443d9ad60b8d909bcfa Author: kejingfan Date: Thu Sep 5 13:31:02 2024 +0800 first commit diff --git a/Assignment/Assignment1/21281280-柯劲帆-课后习题1.pdf b/Assignment/Assignment1/21281280-柯劲帆-课后习题1.pdf new file mode 100644 index 0000000..0e86814 Binary files /dev/null and b/Assignment/Assignment1/21281280-柯劲帆-课后习题1.pdf differ diff --git a/Assignment/Assignment1/source/21281280-柯劲帆-课后习题1.md b/Assignment/Assignment1/source/21281280-柯劲帆-课后习题1.md new file mode 100644 index 0000000..97a9aa2 --- /dev/null +++ b/Assignment/Assignment1/source/21281280-柯劲帆-课后习题1.md @@ -0,0 +1,107 @@ +

课程作业

+ +
+
课程名称:操作系统
+
作业名称:课后习题1
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
+--- + +## 1. 问题1 + +**哲学家进餐问题的变形。假设5支筷子都放在圆桌的中间,哲学家进餐时拿起任意两支筷子就可以进餐。其他条件不变。请用信号量实现5个哲学家进程的同步。** + +```pascal +Var + chopsticks, eating_philosophers: Semaphore := 5, 4; + +Procedure Philosopher(i: integer); +Begin + Repeat + wait(eating_philosophers); + wait(chopsticks); + wait(chopsticks); + + Eat; + + signal(chopsticks); + signal(chopsticks); + signal(eating_philosophers); + + Think; + + Until False; +End; +``` + +## 2. 问题2 + +**四个进程P0,P1,P2,P3和四个信箱M0,M1,M2,M3进程间借助相邻的信箱传递消息:Pi每次从Mi中取出一条消息,经加工送入Mi+1(mod 4)中。其中M0,M1,M2,M3分别设有3,3,2,2个格子,每个格子放一条消息,初始时,M0装满了三条消息,其余为空。写出使用信号量实现进程 (i=0,1,2,3)同步的算法。** + +```pascal +Var + full, empty, mutex: array[0..3] of Semaphore := (3, 0, 0, 0), (0, 3, 2, 2), (1, 1, 1, 1); + +Procedure ProcessPi(i: integer); +Var + message: Message; +Begin + Repeat + wait(full[i]); + wait(mutex[i]); + message := Get_Message_From_Mailbox(i); + signal(mutex[i]); + signal(empty[i]); + + ProcessMessage; + + wait(empty[(i + 1) mod 4]); + wait(mutex[(i + 1) mod 4]); + Send_Message_To_Mail_box((i + 1) mod 4, message); + signal(mutex[(i + 1) mod 4]); + signal(full[(i + 1) mod 4]); + + Until False; +End; +``` + +## 3. 问题3 + +**汽车司机与售票员之间必须协同工作,汽车每到一个站后,一方面,只有当司机已经停下,售票员才能开门上下客,另一方面,只有售票员把车门关好了司机才能开车。假定某辆公共汽车上有前后门各有一名售票员以及一名司机,汽车初始状态是正在始发站停车上客。试设必要的信号量及赋初值,实现售票员进程和司机进程的同步。** + +```pascal +Var + driver, ticket_seller: Semaphore := 1, 2; + +Procedure Driver; +Begin + Repeat + wait(driver); + + Move; + + Stop; + + signal(ticket_seller); + signal(ticket_seller); + + Until False; +End; + +Procedure Ticket_seller(i: integer); +Begin + Repeat + wait(ticket_seller); + wait(ticket_seller); + + Open; + + signal(driver); + + Close; + + Until False; +End; +``` diff --git a/Courseware/1. 操作系统引论-1.ppt b/Courseware/1. 操作系统引论-1.ppt new file mode 100644 index 0000000..2d9a460 Binary files /dev/null and b/Courseware/1. 操作系统引论-1.ppt differ diff --git a/Courseware/1. 操作系统引论-2.ppt b/Courseware/1. 操作系统引论-2.ppt new file mode 100644 index 0000000..e7e2cd1 Binary files /dev/null and b/Courseware/1. 操作系统引论-2.ppt differ diff --git a/Courseware/2. 进程管理-1.ppt b/Courseware/2. 进程管理-1.ppt new file mode 100644 index 0000000..c370d7a Binary files /dev/null and b/Courseware/2. 进程管理-1.ppt differ diff --git a/Courseware/2. 进程管理-2.ppt b/Courseware/2. 进程管理-2.ppt new file mode 100644 index 0000000..ed9511e Binary files /dev/null and b/Courseware/2. 进程管理-2.ppt differ diff --git a/Courseware/2. 进程管理-3.ppt b/Courseware/2. 进程管理-3.ppt new file mode 100644 index 0000000..0faf681 Binary files /dev/null and b/Courseware/2. 进程管理-3.ppt differ diff --git a/Courseware/3. 处理机调度.ppt b/Courseware/3. 处理机调度.ppt new file mode 100644 index 0000000..3a4a9a8 Binary files /dev/null and b/Courseware/3. 处理机调度.ppt differ diff --git a/Courseware/3. 死锁.ppt b/Courseware/3. 死锁.ppt new file mode 100644 index 0000000..bb146db Binary files /dev/null and b/Courseware/3. 死锁.ppt differ diff --git a/Courseware/4.1.连续分配.ppt b/Courseware/4.1.连续分配.ppt new file mode 100644 index 0000000..6784a44 Binary files /dev/null and b/Courseware/4.1.连续分配.ppt differ diff --git a/Courseware/4.2.分页分段.ppt b/Courseware/4.2.分页分段.ppt new file mode 100644 index 0000000..66d224f Binary files /dev/null and b/Courseware/4.2.分页分段.ppt differ diff --git a/Courseware/4.3.虚拟存储器.ppt b/Courseware/4.3.虚拟存储器.ppt new file mode 100644 index 0000000..cf9ac3a Binary files /dev/null and b/Courseware/4.3.虚拟存储器.ppt differ diff --git a/Courseware/5.1.设备管理-概念.ppt b/Courseware/5.1.设备管理-概念.ppt new file mode 100644 index 0000000..2d63785 Binary files /dev/null and b/Courseware/5.1.设备管理-概念.ppt differ diff --git a/Courseware/5.2.设备管理-共性技术.ppt b/Courseware/5.2.设备管理-共性技术.ppt new file mode 100644 index 0000000..5b48f90 Binary files /dev/null and b/Courseware/5.2.设备管理-共性技术.ppt differ diff --git a/Courseware/5.3.设备管理-磁盘.ppt b/Courseware/5.3.设备管理-磁盘.ppt new file mode 100644 index 0000000..1714d6f Binary files /dev/null and b/Courseware/5.3.设备管理-磁盘.ppt differ diff --git a/Courseware/6.1.文件系统.ppt b/Courseware/6.1.文件系统.ppt new file mode 100644 index 0000000..06028d2 Binary files /dev/null and b/Courseware/6.1.文件系统.ppt differ diff --git a/Courseware/6.2.文件系统.ppt b/Courseware/6.2.文件系统.ppt new file mode 100644 index 0000000..6aa604e Binary files /dev/null and b/Courseware/6.2.文件系统.ppt differ diff --git a/Courseware/6.3.文件系统.ppt b/Courseware/6.3.文件系统.ppt new file mode 100644 index 0000000..feefa48 Binary files /dev/null and b/Courseware/6.3.文件系统.ppt differ diff --git a/Lab/Lab1/21281280-柯劲帆-第1次实验.pdf b/Lab/Lab1/21281280-柯劲帆-第1次实验.pdf new file mode 100644 index 0000000..3241b7d Binary files /dev/null and b/Lab/Lab1/21281280-柯劲帆-第1次实验.pdf differ diff --git a/Lab/Lab1/material/21281280-柯劲帆-第1次实验.md b/Lab/Lab1/material/21281280-柯劲帆-第1次实验.md new file mode 100644 index 0000000..cdabab6 --- /dev/null +++ b/Lab/Lab1/material/21281280-柯劲帆-第1次实验.md @@ -0,0 +1,707 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:操作系统并发特征验证性实验
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年10月8日
+
+--- + +## 目录 + +[TOC] + +--- + + + +## 1. 开发运行环境和工具 + +| 操作系统 | Linux内核版本 | 处理器 | GCC版本 | +| :---------: | :-----------------------: | :----------------------------------------------------------: | :-------------------------------: | +| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz (8核,非大小核) | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 | + +- **其他工具**: + - **strace**:version 4.26 + - **编辑**:VSCode (version 1.82.2) + - **编译和运行**:Terminal + + + +## 2. 实验过程 + +### 2.1. 阅读算法 + +**算法逻辑写在以下的代码注释中。** + +**头文件。**三份源码都相同。 + +```cpp +#include +#include +#include +#include +// 引用线程库、标准输入输出库、标准库和时间库 +``` + +**宏定义操作、定义循环变量和共享全局变量。**三份源码都相同。 + +```cpp +typedef char _bool; +#define _true (char)1 +#define _false (char)0 +#define LOG printf("author: 柯劲帆\n") +// 宏定义一些操作 +const int LOOP_NUM = 10000000; +// 定义循环次数 +volatile int nAccount1, nAccount2, turn; +// 创建三个变量用于模拟两个账户和线程并发执行的控制,是临界资源 +// volatile关键字表示这些变量有可能在本程序以外被修改,每次访问这些变量都需要到内存中读取。 +``` + +**主程序。**三份源码都相同。 + +```cpp +int main(int argc, char* argv[]) { + clock_t start, end; + start = clock(); // 记录程序开始时间 + pthread_t th1, th2; + pthread_create(&th1, NULL, transA, NULL); + pthread_create(&th2, NULL, transB, NULL); + // 创建两个线程,分别执行transA和transB + pthread_join(th1, NULL); + pthread_join(th2, NULL); + // 等待两个线程完成 + end = clock(); // 获取程序结束时间 + printf("program running time: %lf\n", ((double)(end - start)) / CLOCKS_PER_SEC); + // 打印程序运行时间,这个时间包括了:1.两个线程实际运行的时间之和;2.被其他CPU任务阻塞的时间 + LOG; + // 输出结束信息 +} +``` + +**none_sync.cpp**的tranA / transB函数。 + +```cpp +void* transA(void* arg) { + int nLoop = 0; // 定义循环变量 + int nTemp1, nTemp2, nRandom; // 定义过程记录变量和随机变量 + do { + nRandom = rand() % 10000; // 为随机变量赋值 + // === 临界区开始 === + nTemp1 = nAccount1; + nTemp2 = nAccount2; + nAccount1 = nTemp1 + nRandom; + nAccount2 = nTemp2 - nRandom; + // 对两个账户进行转账操作 + // === 临界区结束 === + nLoop += 1; + if (nAccount1 + nAccount2 != 0) { + printf("error\n"); + return NULL; + } // 如果两个账户之和不为0,即程序出错,退出程序 + } while (nLoop <= LOOP_NUM); + return arg; +} +``` + +**linux_sync.cpp**的tranA / transB函数。 + +```cpp +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +// 创建一个互斥锁,这个锁将用于保护共享资源的访问,以防止多线程竞争条件 +void* transA(void* arg) +{ + int nLoop = 0; + int nTemp1, nTemp2, nRandom; + do { + pthread_mutex_lock(&mutex); + // 锁定互斥锁以保护临界区 + nRandom = rand() % 10000; + // === 临界区开始 === + nTemp1 = nAccount1; + nTemp2 = nAccount2; + nAccount1 = nTemp1 + nRandom; + nAccount2 = nTemp2 - nRandom; + // === 临界区结束 === + nLoop += 1; + if (nAccount1 + nAccount2 != 0) { + printf("error\n"); + return NULL; + } // 如果两个账户之和不为0,即程序出错,退出程序 + pthread_mutex_unlock(&mutex); + // 解锁互斥锁 + } while (nLoop <= LOOP_NUM); + return arg; +} +``` + +**peterson.cpp**的tranA / transB函数。 + +```cpp +volatile _bool flag[2]; +// 设置旗标变量 +void* transA(void* arg) +{ + int nLoop = 0; + int nTemp1, nTemp2, nRandom; + do { + flag[0] = _true; // 标志本线程已准备好进入临界区 + turn = 1; // 将turn设置为另一个线程的标号 + while (turn == 1 && flag[1] == _true); + // 进入临界区前,检查另一个线程的flag是否为1,且turn不等于自己的标号 + // 如果另一个线程也正在等待,且轮到它,则循环等待 + nRandom = rand() % 10000; + // === 临界区开始 === + nTemp1 = nAccount1; + nTemp2 = nAccount2; + nAccount1 = nTemp1 + nRandom; + nAccount2 = nTemp2 - nRandom; + // === 临界区结束 === + nLoop += 1; + if (nAccount1 + nAccount2 != 0) { + printf("error\n"); + return NULL; + } + flag[0] = _false; + // 当线程执行完临界区代码后,将自己的flag置为0,表示已经离开了临界区 + } while (nLoop <= LOOP_NUM); + return arg; +} +``` + +### 2.2. 编译运行 + +在命令行中分别编译3份代码。命令行提示信息、命令如下。均无输出。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./none_sync.cpp -o ./none_sync -pthread +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./linux_sync.cpp -o ./linux_sync -pthread +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./peterson.cpp -o ./peterson -pthread +``` + +运行第1份程序,命令行提示信息、命令和输出如下。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ ./none_sync +error +error +program running time: 0.000212 +author: 柯劲帆 +``` + +运行第2份程序,命令行提示信息、命令和输出如下。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ ./linux_sync +program running time: 4.596819 +author: 柯劲帆 +``` + +运行第3份程序,命令行提示信息、命令和输出如下。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ ./peterson +program running time: 3.736581 +author: 柯劲帆 +``` + +### 2.3. 修改并运行none_sync.cpp代码 + +修改代码,打印各个变量以观察变量的修改状况。 + +```cpp +do { + nRandom = rand() % 10000; + nTemp1 = nAccount1; + printf("[transA:%d] nTemp1 = %d\n", nLoop, nTemp1); + nTemp2 = nAccount2; + printf("[transA:%d] nTemp2 = %d\n", nLoop, nTemp2); + nAccount1 = nTemp1 + nRandom; + printf("[transA:%d] nAccount1 = %d + %d = %d\n", nLoop, nTemp1, nRandom, nTemp1 + nRandom); + nAccount2 = nTemp2 - nRandom; + printf("[transA:%d] nAccount2 = %d - %d = %d\n", nLoop, nTemp2, nRandom, nTemp2 - nRandom); + nLoop += 1; + if (nAccount1 + nAccount2 != 0) { + printf("error\n"); + printf("[transA] nTemp1=%d, nTemp2=%d, nRandom=%d, nTemp1+nRandom=%d, nTemp2-nRandom=%d, nAccount1=%d, nAccount2=%d\n", nTemp1, nTemp2, nRandom, nTemp1 + nRandom, nTemp2 - nRandom, nAccount1, nAccount2); + return NULL; + } +} while (nLoop <= LOOP_NUM); +``` + +编译并运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./none_sync.cpp -o ./none_sync -pthread && ./none_sync +``` + +截取error部分和main部分的输出如下: + +``` +error +[transA] nTemp1=41171, nTemp2=-21, nRandom=59, nTemp1+nRandom=41230, nTemp2-nRandom=-80, nAccount1=41230, nAccount2=-80 +error +[transB] nTemp1=16, nTemp2=-16, nRandom=5, nTemp1+nRandom=21, nTemp2-nRandom=-21, nAccount1=41230, nAccount2=-80 +program running time: 0.000237 +author: 柯劲帆 +``` + +**完整输出见附录”7.1. none_sync_1程序的完整输出“。** + +### 2.4. 修改并运行linux_sync.cpp和peterson.cpp代码 + +将linux_sync.cpp代码的do-while循环部分修改如下。 + +```cpp +do { + pthread_mutex_lock(&mutex); + nRandom = rand() % 10; + nTemp1 = nAccount1; + nTemp2 = nAccount2; + nAccount1 = nTemp1 + nRandom; + nAccount2 = nTemp2 - nRandom; + pthread_mutex_unlock(&mutex); +} while ((nAccount1 + nAccount2) == 0); +``` + +编译并运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./linux_sync_1.cpp -o ./none_sync_1 -pthread && ./linux_sync_1 +``` + +程序一直运行不停止,直至在命令行输入`ctrl+C`才退出。 + +将peterson.cpp代码的transA函数do-while循环部分修改如下。 + +```cpp +do { + flag[0] = _true; + turn = 1; + while (turn == 1 && flag[1] == _true); + nRandom = rand() % 10000; + nTemp1 = nAccount1; + nTemp2 = nAccount2; + nAccount1 = nTemp1 + nRandom; + nAccount2 = nTemp2 - nRandom; + flag[0] = _false; +} while ((nAccount1 + nAccount2) == 0); +``` + +transB函数作类似更改,不同之处在于上述代码第3行`turn = 0;`和第4行`while (turn == 0 && flag[0] == _true);`。 + +编译并运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./peterson_1.cpp -o ./peterson_1 -pthread && ./peterson_1 +``` + +程序一直运行不停止,直至在命令行输入`ctrl+C`才退出。 + +### 2.5. 分别多次运行linux_sync程序和peterson程序比较运行时间 + +多次在命令行中运行linux_sync程序和peterson程序,将输出的运行时间记录。 + +所有的运行结果均没有error提示信息打印出来,说明以下的运行时间均为do-while循环内临界区运行了一共$2 \times 10000000$次的时间。 + +| 程序 | 第1次运行用时/s | 第2次运行用时/s | 第3次运行用时/s | 第4次运行用时/s | 第5次运行用时/s | 第6次运行用时/s | 平均运行用时/s | +| :------------: | :-------------: | :-------------: | :-------------: | :-------------: | :-------------: | :-------------: | :------------: | +| **linux_sync** | 5.829870 | 3.351867 | 4.729514 | 3.930167 | 5.229939 | 4.624409 | **4.615961** | +| **peterson** | 3.757013 | 3.428822 | 3.420579 | 2.483765 | 5.445688 | 4.487762 | **3.837272** | + +### 2.6. 使用strace工具观察linux_sync程序和peterson程序的系统调用情况 + +在终端中调用strace工具分析linux_sync程序和peterson程序的系统调用情况。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ strace -c ./linux_sync +program running time: 4.172697 +author: 柯劲帆 +% time seconds usecs/call calls errors syscall +------ ----------- ----------- --------- --------- ---------------- + 99.99 1.971464 1971464 1 futex + 0.00 0.000060 2 29 mmap + 0.00 0.000030 2 12 mprotect + 0.00 0.000022 11 2 clone + 0.00 0.000013 2 6 openat + 0.00 0.000008 8 1 munmap + 0.00 0.000006 1 5 read + 0.00 0.000006 1 6 close + 0.00 0.000006 0 7 fstat + 0.00 0.000005 2 2 write + 0.00 0.000004 2 2 clock_gettime + 0.00 0.000003 1 3 brk + 0.00 0.000003 3 1 1 access + 0.00 0.000002 1 2 rt_sigaction + 0.00 0.000002 2 1 execve + 0.00 0.000001 1 1 rt_sigprocmask + 0.00 0.000001 1 1 arch_prctl + 0.00 0.000001 1 1 set_tid_address + 0.00 0.000001 1 1 set_robust_list + 0.00 0.000001 1 1 prlimit64 +------ ----------- ----------- --------- --------- ---------------- +100.00 1.971639 85 1 total +``` +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ strace -c ./peterson +program running time: 4.891933 +author: 柯劲帆 +% time seconds usecs/call calls errors syscall +------ ----------- ----------- --------- --------- ---------------- + 0.00 0.000000 0 5 read + 0.00 0.000000 0 2 write + 0.00 0.000000 0 6 close + 0.00 0.000000 0 7 fstat + 0.00 0.000000 0 29 mmap + 0.00 0.000000 0 12 mprotect + 0.00 0.000000 0 1 munmap + 0.00 0.000000 0 3 brk + 0.00 0.000000 0 2 rt_sigaction + 0.00 0.000000 0 1 rt_sigprocmask + 0.00 0.000000 0 1 1 access + 0.00 0.000000 0 2 clone + 0.00 0.000000 0 1 execve + 0.00 0.000000 0 1 arch_prctl + 0.00 0.000000 0 1 futex + 0.00 0.000000 0 1 set_tid_address + 0.00 0.000000 0 2 clock_gettime + 0.00 0.000000 0 6 openat + 0.00 0.000000 0 1 set_robust_list + 0.00 0.000000 0 1 prlimit64 +------ ----------- ----------- --------- --------- ---------------- +100.00 0.000000 85 1 total +``` + +使用taskset命令,将linux_sync程序的线程绑定到7号CPU上运行,并使用strace工具进行监视系统调用情况。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ strace -c taskset -c 7 ./linux_sync +program running time: 0.451617 +author: 柯劲帆 +% time seconds usecs/call calls errors syscall +------ ----------- ----------- --------- --------- ---------------- + 81.46 0.002184 1092 2 futex + 9.85 0.000264 132 2 clone + 3.25 0.000087 2 37 mmap + 1.49 0.000040 2 16 mprotect + 0.82 0.000022 2 9 openat + 0.63 0.000017 17 1 sched_setaffinity + 0.48 0.000013 6 2 munmap + 0.34 0.000009 1 6 read + 0.34 0.000009 0 10 fstat + 0.30 0.000008 0 9 close + 0.30 0.000008 1 6 brk + 0.22 0.000006 3 2 2 access + 0.11 0.000003 1 2 execve + 0.07 0.000002 1 2 rt_sigaction + 0.07 0.000002 1 2 arch_prctl + 0.07 0.000002 2 1 sched_getaffinity + 0.04 0.000001 1 1 rt_sigprocmask + 0.04 0.000001 1 1 set_tid_address + 0.04 0.000001 0 2 clock_gettime + 0.04 0.000001 1 1 set_robust_list + 0.04 0.000001 1 1 prlimit64 + 0.00 0.000000 0 2 write +------ ----------- ----------- --------- --------- ---------------- +100.00 0.002681 117 2 total +``` + +发现linux_sync程序运行速度非常快,用时接近无绑定CPU运行时间的$10\%$。比较strace显示的系统调用用时,发现futex用时减少显著。 + +说明在futex调度多个CPU的过程中产生了大量的时间开销。 + +同理使用taskset命令,将peterson程序的线程绑定到7号CPU上运行,并使用strace工具进行监视系统调用情况。 + + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ strace -c taskset -c 7 ./peterson +^Cstrace: Process 26667 detached +% time seconds usecs/call calls errors syscall +------ ----------- ----------- --------- --------- ---------------- + 0.00 0.000000 0 6 read + 0.00 0.000000 0 9 close + 0.00 0.000000 0 9 fstat + 0.00 0.000000 0 37 mmap + 0.00 0.000000 0 16 mprotect + 0.00 0.000000 0 2 munmap + 0.00 0.000000 0 6 brk + 0.00 0.000000 0 2 rt_sigaction + 0.00 0.000000 0 1 rt_sigprocmask + 0.00 0.000000 0 2 2 access + 0.00 0.000000 0 2 clone + 0.00 0.000000 0 2 execve + 0.00 0.000000 0 2 arch_prctl + 0.00 0.000000 0 1 1 futex + 0.00 0.000000 0 1 sched_setaffinity + 0.00 0.000000 0 1 sched_getaffinity + 0.00 0.000000 0 1 set_tid_address + 0.00 0.000000 0 1 clock_gettime + 0.00 0.000000 0 9 openat + 0.00 0.000000 0 1 set_robust_list + 0.00 0.000000 0 1 prlimit64 +------ ----------- ----------- --------- --------- ---------------- +100.00 0.000000 112 3 total +``` + +发现在单颗CPU上运行的peterson程序运行非常缓慢;futex只被调用1次且用时忽略不计。 + +将该程序提前结束。 + +减小`LOOP_NUM`的大小,观察导致单核中peterson程序运行缓慢的原因。 + +在源代码中修改`LOOP_NUM`的大小。 + +```cpp +// const int LOOP_NUM = 10000000; +const int LOOP_NUM = 1000; +``` + +编译并运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./peterson.cpp -o ./peterson -pthread && taskset -c 7 ./peterson +program running time: 0.000046 +author: 柯劲帆 +``` + +程序正常地快速运行完毕了。 + +不断调整循环次数`LOOP_NUM`的大小,发现当`LOOP_NUM`为10^3的数量级或以下时,程序正常快速运行并结束;当`LOOP_NUM`为10^5的数量级以上时,程序运行极为缓慢;`LOOP_NUM`在10^3和10^5的数量级之间时,程序有时快速运行并结束,有时运行非常慢,且受到是否使用strace工具监视的影响。 + +上述实验结果记录如下: + +| `LOOP_NUM` | 10^2 | 10^3 | 10^4 | 10^5 | 10^6 | 10^7 | +| :------------------------: | :-----------: | :-----------: | :-----------: | :-----------: | :---------: | :---------: | +| 直接运行程序用时 | $0.000027$ 秒 | $0.000032$ 秒 | $0.000081$ 秒 | $0.001459$ 秒 | 超过$1$分钟 | 超过$1$分钟 | +| 使用strace工具监视程序用时 | $0.000037$ 秒 | $0.000051$ 秒 | $0.000040$ 秒 | 超过$1$分钟 | 超过$1$分钟 | 超过$1$分钟 | + +可以看出,循环次数的数量级的增加导致了运行时间的大量非线性增加,由此猜测是线程运行时间的长短导致了操作系统采用了不同的并发执行策略,导致了总的程序执行时间产生了非线性变化。 + +因此我又做了如下实验: + +修改代码,在peterson程序中每个临界区结束时,打印输出线程编号和`nLoop`值。分别设置`LOOP_NUM`为10^3和10^7,编译和运行peterson程序。(限于篇幅,此处省略程序输出的展示,仅用文字说明结果) + +观察发现,当`LOOP_NUM`为10^3时,两个线程并不是交错执行的,而是当transA线程循环结束时,transB线程才开始循环,相当于串行执行;但是当`LOOP_NUM`为10^7时,首先transA线程循环了1329次后,transB线程才开始循环,接下来两个线程交错执行,如下是截取的部分输出: + +(每行两个数。第一个数为1表示该行由transA输出,第一个数为2表示该行由transB输出;第二个数表示该行是在对应线程的第几个循环输出的。) + +```txt +1 1326 +1 1327 +1 1328 +1 1329 +2 1 +1 1330 +2 2 +1 1331 +2 3 +1 1332 +2 4 +1 1333 +``` + +说明影响单颗CPU上peterson程序运行时间大幅度变化的原因是: + +- 单个线程运行时间较短时(如`LOOP_NUM`为10^3),操作系统倾向于让线程串行计算,减少了切换开销; +- 当单个线程独占CPU的运行时间达到一定长度时,操作系统会将另一个线程插入,让两个线程并发运行。因此当线程运行时间较长时(如`LOOP_NUM`为10^7),在第一个线程运行一段时间后,另一个线程也开始运行,两个线程开始频繁争抢CPU时间片,切换开销变大,导致运行速度急剧下降; +- strace工具会增加计算开销,影响操作系统的决策。在`LOOP_NUM`为10^4到10^5的数量级时,当该CPU上的运行任务较多,串行运行的线程可能提前开始与其他线程并行运行,导致运行速度急剧下降。 + + + +## 3. 实验分析 + +### 3.1. none_sync算法错误原因分析 + +分析修改了输出信息的none_sync_1.cpp编译的程序,作出时序执行图: + +graph.drawio + +出错的原因是: + +**在无同步控制的情况下,当两个线程同时执行转账操作时,会出现数据竞争的问题。** + +**具体来说,transA线程读取了账户余额,此时transB线程也读取了相同的账户余额,然后两线程基于读取的余额做计算,最后写入结果。这会导致计算错误,总额不再相等。** + +### 3.2. 后两种算法正确的原因分析 + +#### 3.2.1. Linux操作系统同步机制正确的原因 + +**语句`pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;`创建的互斥锁保护了共享资源的访问,防止多线程竞争。** + +当transA线程即将进入临界区时,`pthread_mutex_lock(&mutex);`语句调用系统接口锁定互斥锁,保护临界区,直至临界区执行完毕。在这个过程中,transB线程都无法进入临界区,从而保护了共享资源nAccount1和nAccount2变量在transA线程运行的过程中不会被transB修改。同理,transB线程锁定互斥锁后,transA无法进入临界区。 + +#### 3.2.2. Peterson算法正确的原因 + +将**进入临界区前**的并行执行情况枚举如下(假设当前没有线程执行临界区,flag都为false): + +| 步骤1 | 步骤2 | 步骤3 | 步骤4 | 步骤5 | 步骤6 | 步骤7 | 进入 | +| :-----------------: | :-------------------: | :--------------------: | :------------------: | :------------------: | :-------------------: | :-------------------: | :----: | +| `A` flag[0] = _true | `A` turn = 1 | `A` while () --> false | | | | | transA | +| `A` flag[0] = _true | `A` turn = 1 | `B` flag[1] = _true | `B` turn = 0 | `B` while() --> true | `A` while() --> false | | transA | +| `A` flag[0] = _true | `B` flag[1] = _true | `B` turn = 0 | `B` while() --> true | `A` turn = 1 | `A` while() --> true | `B` while() --> false | transB | +| `A` flag[0] = _true | `B` flag[1] = _true | `A` turn = 1 | `A` while() --> true | `B` turn = 0 | `B` while() --> true | `A` while() --> false | transA | +| `A` flag[0] = _true | `B` turn = 0 | `B` while() --> true | `A` turn = 1 | `A` while() --> true | `B` while() --> false | | transB | +| `A` flag[0] = _true | `B` while() --> false | | | | | | transB | + +以上是transA先执行第1行do-while代码的大部分情况枚举(由对称性,transB先执行第1行do-while代码的情况同理)。 + +当任意一个线程(如transA)**在临界区内执行时**,该线程(如transA)对应的flag都为true,另外一个线程(如transB)在判断是否要等待的while语句前,turn都会被赋值为正在执行临界区的线程(如transA)的编号。因此未进入临界区的线程(如transB)会陷入while循环等待。 + +显然,peterson算法的逻辑可以使得在同一时刻执行临界区的线程数不超过1个,且不会发生所有线程都在while等待的情况。 + +### 3.3. Peterson 算法或Linux操作系统同步机制出现问题的原因分析 + +Peterson 算法或Linux操作系统同步机制的代码不会导致$\rm{nAccount1} + \rm{nAccount2} \ne 0$,因此不加循环变量的判别不会跳出循环,导致程序死循环。 + +因此将循环设置为`nLoop < 1000000`,并用`if (nAccount1 + nAccount2 != 0) break;`语句实现原循环条件中的判别功能。 + +### 3.4. 两种同步机制的效率比较 + +从**运行机制**上来看,Linux操作系统同步机制比Peterson算法相对效率更高。 + +- Peterson算法通过旗标变量和轮转变量实现互斥,需要线程自己判断是否获得访问权限,需要更多的用户态运算来实现同步。 + +- 操作系统同步机制通过内核级锁实现互斥,由操作系统直接管理同步访问,线程直接获取和释放访问权限,减少了用户态和内核态之间的切换,也不需要线程自己实现同步逻辑,效率更高。 + +但是从实验中的**实际运行**时间上看,Peterson算法相对Linux操作系统同步机制效率更高。 + +下面通过分析实验过程的现象比较两种同步机制的效率: + +1. 在使用strace工具观察linux_sync程序和peterson程序的系统调用情况的过程中,分析发现耗时最多的系统调用是futex。linux_sync程序调用了两次futex,总共用时较多,而peterson程序只调用了一次futex,用时忽略不计。 + + 查阅资料[[1]](https://linux.die.net/man/2/futex)可知,futex用于等待给定地址的值发生更改,并为唤醒在特定地址上等待的任何线程提供了一个方法,用于实现共享内存中锁的争用情况。 + + 在linux_sync程序中,第二个线程执行前访问了futex变量,检测到第一个线程被锁定了,因此进入等待状态,并在第一个线程完成后被唤醒。这样的机制使得linux_sync程序实现内核级锁实现互斥,减少用户态和内核态之间的切换,效率更高。而peterson程序只调用了一次futex,在该项系统调用内并行完成了两个线程。 + +2. 在将线程绑定到单个CPU上运行的实验中,发现linux_sync程序运行速度非常快,用时接近无绑定CPU运行时间的$10\%$;但是peterson程序运行非常缓慢。 + + - linux_sync程序:在多核并行运行时,linux_sync程序需要通过系统调用futex影响CPU的调度,这个调度过程消耗了大量的时间,但是当绑定单个CPU之后这个调度过程被简化了,因此运行时间大大减少。 + + - peterson程序:当循环次数到10^3量级以上时,操作系统会让两个线程交替执行,线程切换开销较大,导致绑定单个CPU之后,peterson程序运行非常缓慢;但是不绑定CPU,即使有CPU调度的开销,多核并发的贡献能抵消调度开销,大大减少peterson程序运行的时间。 + +3. 直接在命令行运行linux_sync程序和peterson程序,多次运行,通过比较运行时间反映两种算法或机制的运行效率。从实际运行时间上看,peterson算法相对linux操作系统同步机制效率更高。 + +根据以上现象和资料,总结出以下的原因导致Peterson算法程序的运行时间比linux_sync程序的运行时间短: + +- peterson程序中没有使用系统调用,核心运算也非常简单,所以系统调度更具灵活性,有助于多核并发,弥补了CPU调度开销和线程切换开销; +- linux_sync程序进行了系统调用,存在大量CPU的调度时间开销。 + + + +## 5. 问题与讨论 + +### 5.1. 编译问题 + +使用g++编译3份代码时,需要加`-pthread`参数,如下。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./none_sync.cpp -o ./none_sync -pthread +``` + +否则, + +```shell +(base) kejingfan@KJF-Huawei-PC:~/code$ g++ ./none_sync.cpp -o ./none_sync +/usr/bin/ld: /tmp/ccrfomkX.o: in function `main': +none_sync.cpp:(.text+0x18e): undefined reference to `pthread_create' +/usr/bin/ld: none_sync.cpp:(.text+0x1a9): undefined reference to `pthread_create' +/usr/bin/ld: none_sync.cpp:(.text+0x1ba): undefined reference to `pthread_join' +/usr/bin/ld: none_sync.cpp:(.text+0x1cb): undefined reference to `pthread_join' +collect2: error: ld returned 1 exit status +``` + +这可能是由于操作系统所带的g++ (Uos 8.3.0.3-3+rebuild) 8.3.0版本特性导致的。我在g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0版本上并未发现不加`-pthread`参数会报错。 + +`-pthread` 参数是告诉g++编译器链接POSIX线程库的选项。POSIX线程库是一种用于多线程编程的标准库,也被称为pthreads(POSIX Threads)。它提供了创建、管理和同步线程的函数和数据结构[[2]]([pthreads - Wikipedia](https://en.wikipedia.org/wiki/Pthreads))。当在代码中使用多线程功能,如 `pthread_create` 和 `pthread_join` 时,需要链接 pthreads 库,以便编译器能够找到这些函数的定义并正确连接它们到可执行文件中。如果不加 `-pthread` 参数,编译器可能会产生错误消息,因为它无法找到对应的线程函数定义。 + + + +## 6. 实验结论 + +- 不加线程同步机制的并行线程在访问共享资源时容易产生数据竞争,会导致运算错误等一系列问题。开发者需要在程序开发中注意线程并行同步机制的问题,注意避免数据竞争,保护共享资源。 +- 多线程并发过程中的主要额外时间开销是进程切换开销和CPU调度开销,开发者需要将这两个开销与多线程并行计算和多核并行计算的效率优势进行权衡。 +- 不同的线程同步机制在实现上有区别,如Peterson算法主要依靠程序逻辑实现,而Linux内核则是通过系统调用实现互斥机制。同一同步机制在不同条件下(如不同程序、不同系统)效率也可能有所不同,因为这些条件既会影响线程切换和CPU切换的开销,也会影响计算速度等别的因素。选择合适的机制可以提高效率。 + + + +## 7. 附录 + +### 7.1. none_sync_1程序的完整输出 + + +```txt +(base) kejingfan@KJF-Huawei-PC:~/code$ ./linux_sync +[transB:0] nTemp1 = 0 +[transB:0] nTemp2 = 0 +[transB:0] nAccount1 = 0 + 3 = 3 +[transB:0] nAccount2 = 0 - 3 = -3 +[transB:1] nTemp1 = 3 +[transB:1] nTemp2 = -3 +[transB:1] nAccount1 = 3 + 6 = 9 +[transB:1] nAccount2 = -3 - 6 = -9 +[transB:2] nTemp1 = 9 +[transB:2] nTemp2 = -9 +[transB:2] nAccount1 = 9 + 7 = 16 +[transB:2] nAccount2 = -9 - 7 = -16 +[transB:3] nTemp1 = 16 +[transB:3] nTemp2 = -16 +[transA:0] nTemp1 = 16 +[transA:0] nTemp2 = -16 +[transA:0] nAccount1 = 16 + 7793 = 7809 +[transA:0] nAccount2 = -16 - 7793 = -7809 +[transA:1] nTemp1 = 7809 +[transA:1] nTemp2 = -7809 +[transA:1] nAccount1 = 7809 + 8335 = 16144 +[transA:1] nAccount2 = -7809 - 8335 = -16144 +[transA:2] nTemp1 = 16144 +[transA:2] nTemp2 = -16144 +[transA:2] nAccount1 = 16144 + 5386 = 21530 +[transA:2] nAccount2 = -16144 - 5386 = -21530 +[transA:3] nTemp1 = 21530 +[transA:3] nTemp2 = -21530 +[transA:3] nAccount1 = 21530 + 492 = 22022 +[transA:3] nAccount2 = -21530 - 492 = -22022 +[transA:4] nTemp1 = 22022 +[transA:4] nTemp2 = -22022 +[transA:4] nAccount1 = 22022 + 6649 = 28671 +[transA:4] nAccount2 = -22022 - 6649 = -28671 +[transA:5] nTemp1 = 28671 +[transA:5] nTemp2 = -28671 +[transA:5] nAccount1 = 28671 + 1421 = 30092 +[transA:5] nAccount2 = -28671 - 1421 = -30092 +[transA:6] nTemp1 = 30092 +[transA:6] nTemp2 = -30092 +[transA:6] nAccount1 = 30092 + 2362 = 32454 +[transA:6] nAccount2 = -30092 - 2362 = -32454 +[transA:7] nTemp1 = 32454 +[transA:7] nTemp2 = -32454 +[transA:7] nAccount1 = 32454 + 27 = 32481 +[transA:7] nAccount2 = -32454 - 27 = -32481 +[transA:8] nTemp1 = 32481 +[transA:8] nTemp2 = -32481 +[transA:8] nAccount1 = 32481 + 8690 = 41171 +[transA:8] nAccount2 = -32481 - 8690 = -41171 +[transB:3] nAccount1 = 16 + 5 = 21 +[transB:3] nAccount2 = -16 - 5 = -21 +[transA:9] nTemp1 = 41171 +[transA:9] nTemp2 = -21 +[transA:9] nAccount1 = 41171 + 59 = 41230 +[transA:9] nAccount2 = -21 - 59 = -80 +error +[transA] nTemp1=41171, nTemp2=-21, nRandom=59, nTemp1+nRandom=41230, nTemp2-nRandom=-80, nAccount1=41230, nAccount2=-80 +error +[transB] nTemp1=16, nTemp2=-16, nRandom=5, nTemp1+nRandom=21, nTemp2-nRandom=-21, nAccount1=41230, nAccount2=-80 +program running time: 0.000237 +author: 柯劲帆 +``` + +### 7.2. 参考资料 + +[[1]](https://linux.die.net/man/2/futex) The futex() system call provides a method for a program to wait for a value at a given address to change, and a method to wake up anyone waiting on a particular address (while the addresses for the same memory in separate processes may not be equal, the kernel maps them internally so the same memory mapped in different locations will correspond for futex() calls). This system call is typically used to implement the contended case of a lock in shared memory. + +[[2]]([pthreads - Wikipedia](https://en.wikipedia.org/wiki/Pthreads)) In computing, POSIX Threads, commonly known as pthreads, is an execution model that exists independently from a programming language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API. diff --git a/Lab/Lab1/material/21281280-柯劲帆-第1次实验.pdf b/Lab/Lab1/material/21281280-柯劲帆-第1次实验.pdf new file mode 100644 index 0000000..3241b7d Binary files /dev/null and b/Lab/Lab1/material/21281280-柯劲帆-第1次实验.pdf differ diff --git a/Lab/Lab1/material/graph.drawio b/Lab/Lab1/material/graph.drawio new file mode 100644 index 0000000..cbd708d --- /dev/null +++ b/Lab/Lab1/material/graph.drawio @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lab/Lab1/material/graph.drawio.png b/Lab/Lab1/material/graph.drawio.png new file mode 100644 index 0000000..06a7485 Binary files /dev/null and b/Lab/Lab1/material/graph.drawio.png differ diff --git a/Lab/Lab1/material/graph.drawio.svg b/Lab/Lab1/material/graph.drawio.svg new file mode 100644 index 0000000..948e08a --- /dev/null +++ b/Lab/Lab1/material/graph.drawio.svg @@ -0,0 +1,4 @@ + + + +
transB线程开始
transB线程开始
[0] nTemp1 = nAccount1;
[0] nTemp1 = nAccount1;
nTemp1 = 0
nTemp1 = 0
transA线程开始
transA线程开始
[0] nTemp1 = nAccount1;
[0] nTemp1 = nAccount1;
nTemp1 = 16
nTemp1 = 16
[0] nTemp2 = nAccount2;
[0] nTemp2 = nAccount2;
nTemp2 = 0
nTemp2 = 0
[0] nAccount1 = nTemp1 + nRandom;
[0] nAccount1 = nTemp1 + nRandom;
nTemp1 = 0
nTemp1 = 0
nAccount1 = 0 + 3 = 3
nAccount1 = 0 + 3 = 3
[0] nAccount2 = nTemp2 - nRandom;
[0] nAccount2 = nTemp2 - nRandom;
nTemp2 = 0
nTemp2 = 0
nAccount2 = 0 - 3 = -3
nAccount2 = 0 - 3 = -3
[3] nTemp1 = nAccount1;
[3] nTemp1 = nAccount1;
nTemp1 = 16
nTemp1 = 16
[3] nTemp2 = nAccount2;
[3] nTemp2 = nAccount2;
nTemp2 = -16
nTemp2 = -16
[1]  ... [2]
[1]  ... [2]
nAccount1 = 0
nAccount2 = 0
nAccount1 = 0...
nAccount1 = 0
nAccount2 = 0
nAccount1 = 0...
nAccount1 = 16
nAccount2 = -16
nAccount1 = 16...
nAccount1 = 16
nAccount2 = -16
nAccount1 = 16...
nAccount1 = 16
nAccount2 = -16
nAccount1 = 16...
[0] nTemp2 = nAccount2;
[0] nTemp2 = nAccount2;
nTemp2 = -16
nTemp2 = -16
nAccount1 = 16
nAccount2 = -16
nAccount1 = 16...

共享资源

共享资源
nAccount1 = 41171
nAccount2 = -41171
nAccount1 = 41171...
[8] nAccount2 = nTemp2 - nRandom;
[8] nAccount2 = nTemp2 - nRandom;
nTemp2 = -32481
nTemp2 = -32481
nAccount2 = -32481 - 8690 = -41171
nAccount2 = -32481 - 8690 = -41171
[3] nAccount1 = nTemp1 + nRandom;
[3] nAccount1 = nTemp1 + nRandom;
nTemp1 = 16
nTemp1 = 16
nAccount1 = 16 + 5 = 21
nAccount1 = 16 + 5 = 21
[3] nAccount2 = nTemp2 - nRandom;
[3] nAccount2 = nTemp2 - nRandom;
nTemp2 = -16
nTemp2 = -16
nAccount2 = -16 - 5 = -21
nAccount2 = -16 - 5 = -21
[9] nTemp1 = nAccount1;
[9] nTemp1 = nAccount1;
nTemp1 = 41171
nTemp1 = 41171
[9] nTemp2 = nAccount2;
[9] nTemp2 = nAccount2;
nTemp2 = -21
nTemp2 = -21
nAccount1 = 21
nAccount2 = -21
nAccount1 = 21...
[8] nAccount1 = nTemp1 + nRandom;
[8] nAccount1 = nTemp1 + nRandom;
nTemp1 = 41171
nTemp1 = 41171
nAccount1 = 41171 + 59 = 41230
nAccount1 = 41171 + 59 = 41230
[8] nAccount2 = nTemp2 - nRandom;
[8] nAccount2 = nTemp2 - nRandom;
nTemp2 = -21
nTemp2 = -21
nAccount2 = -21 - 59 = -80
nAccount2 = -21 - 59 = -80
[8] nAccount1 + nAccount2 != 0
[8] nAccount1 + nAccount2 != 0
printf(nAccount1) --> 41230
printf(nAccount1) --> 41230
printf(nAccount2) --> -80
printf(nAccount2) --> -80
[3] nAccount1 + nAccount2 != 0
[3] nAccount1 + nAccount2 != 0
printf(nAccount1) --> 41230
printf(nAccount1) --> 41230
printf(nAccount2) --> -80
printf(nAccount2) --> -80
nAccount1 = 41230
nAccount2 = -80
nAccount1 = 41230...
transA线程结束
transA线程结束
transB线程结束
transB线程结束
nAccount1 = 41230
nAccount2 = -80
nAccount1 = 41230...

共享资源

共享资源
[1]  ... [8]
[1]  ... [8]
[8] nAccount1 = nTemp1 + nRandom;
[8] nAccount1 = nTemp1 + nRandom;
nTemp1 = 32481
nTemp1 = 32481
nAccount1 = 32481 + 8690 = 41171
nAccount1 = 32481 + 8690 = 41171
Text is not SVG - cannot display
\ No newline at end of file diff --git a/Lab/Lab1/material/graph.jpg b/Lab/Lab1/material/graph.jpg new file mode 100644 index 0000000..b94a18d Binary files /dev/null and b/Lab/Lab1/material/graph.jpg differ diff --git a/Lab/Lab1/第一次操作系统实验要求.docx b/Lab/Lab1/第一次操作系统实验要求.docx new file mode 100644 index 0000000..d55dedd Binary files /dev/null and b/Lab/Lab1/第一次操作系统实验要求.docx differ diff --git a/Lab/Lab2/21281280-柯劲帆-第2次实验.pdf b/Lab/Lab2/21281280-柯劲帆-第2次实验.pdf new file mode 100644 index 0000000..580aaec Binary files /dev/null and b/Lab/Lab2/21281280-柯劲帆-第2次实验.pdf differ diff --git a/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.docx b/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.docx new file mode 100644 index 0000000..0ffaaff Binary files /dev/null and b/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.docx differ diff --git a/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.pdf b/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.pdf new file mode 100644 index 0000000..981396c Binary files /dev/null and b/Lab/Lab2/requirements/第2次实验-典型同步问题算法验证实验.pdf differ diff --git a/Lab/Lab2/source/21281280-柯劲帆-第2次实验.md b/Lab/Lab2/source/21281280-柯劲帆-第2次实验.md new file mode 100644 index 0000000..b1a0960 --- /dev/null +++ b/Lab/Lab2/source/21281280-柯劲帆-第2次实验.md @@ -0,0 +1,1565 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:典型同步问题验证性实验
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年10月12日
+
+ +--- + +## 目录 + +[TOC] + +--- + + + +# 1. 开发运行环境和工具 + +| 操作系统 | Linux内核版本 | 处理器 | GCC版本 | +| :---------: | :-----------------------: | :----------------------------------------------------------: | :-------------------------------: | +| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz (8核,非大小核) | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 | + +- **其他工具**: + - **编辑**:VSCode (version 1.83.1) + - **编译和运行**:Terminal + + + +# 2. 实验过程、分析和结论 + +## 2.1. 阅读算法 + +**算法实现的思路写在以下的代码注释中。** + +### 2.1.1. producer-consumer + +```c +#include +#include +#include +#include +#define n 5 + +// ===临界资源=== +int in = 0, out = 0; +// in和out变量分别表示下一个要写入数据的位置和下一个要读取数据的位置。 +int buffer[n]; // 缓冲 +// buffer数组表示缓冲池。生产者将生产的产品放入缓冲区的一个位置,消费者从缓冲区中取出产品进行消费 +// ===临界资源=== + +// empty: 资源信号量,表示缓冲池中空缓冲区的数量,其初始值为n +// full: 资源信号量,表示缓冲池中有数据的缓冲区的数量,其初始值为0 +// mutex: 互斥信号量,实现进程对缓冲池的互斥使用 +sem_t empty, full, mutex; +// mutex信号量用于实现对缓冲区的互斥访问。在生产者和消费者访问缓冲区时,mutex信号量保证了同一时刻只有一个线程可以进入临界区(访问缓冲区) +// empty信号量用于表示空缓冲区的数量,初始值为缓冲区的大小。当一个生产者想要将产品放入缓冲区时,它会尝试获取empty信号量。如果empty大于0,说明有空缓冲区,生产者可以放入产品,然后empty减1 +// full信号量用于表示非空缓冲区的数量,初始值为0。当一个消费者想要从缓冲区中取出产品时,它会尝试获取full信号量。如果full大于0,说明有产品可以被消费,消费者可以取出产品,然后full减1 + +// ===临界资源=== +int id = 0; // 记录产品的id +// ===临界资源=== + +// 生产者 +void* producer(void* param) { + long ThreadId = (long)param; // 线程id + while (1) { + sem_wait(&empty); // wait(empty) + sem_wait(&mutex); // wait(mutex) + // 生产者线程首先获取empty信号量,如果empty大于0,说明有空缓冲区,可以放入产品;然后获取mutex信号量,与消费者互斥访问临界区 + // 生产产品 + buffer[in] = id; // 生产者在缓冲区的下一个位置放入产品 + in = (in + 1) % n; // 缓冲区输入下标变量向后移动一位,以便下次放入下一个位置的缓冲区 + sleep(2); // 生产产品花费时间 + printf("Thread-%ld : Producer produce product %d \n", ThreadId, id++); + sem_post(&mutex); // signal(mutex) + sem_post(&full); // signal(full) + // 生产者放入产品后,释放mutex信号量,表示生产者退出临界区;并增加full信号量,表示缓冲区中有产品了 + } +} + +// 消费者 +void* consumer(void* param) { + long ThreadId = (long)param; + while (1) { + sem_wait(&full); // wait(full) + sem_wait(&mutex); // wait(mutex) + // 消费者线程首先尝试获取full信号量,如果full大于0,说明有产品可以被消费;然后获取mutex信号量,与生产者互斥访问临界区 + // 消费产品 + int item = buffer[out]; //消费者从缓冲区的下一个位置取出产品 + out = (out + 1) % n; //缓冲区输出下标变量向后移动一位,以便下次取出下一个位置的缓冲区的产品 + sleep(1); // 消费产品花费时间 + printf("Thread-%ld : Consumer consume product %d \n", ThreadId, item); + sem_post(&mutex); // signal(mutex) + sem_post(&empty); // signal(empty) + // 消费者取出产品后,释放mutex信号量,表示消费者退出临界区;并增加empty信号量,表示缓冲区中有空缓冲区了 + } +} + +int main() { + // 线程id + pthread_t tid[4]; + + // 初始化信号量 + // 第二个参数用于区分进程/线程间共享(0为当前进程各线程间共享) + // 第三个参数为信号量初始值 + sem_init(&mutex, 0, 1); + sem_init(&empty, 0, n); + sem_init(&full, 0, 0); + + // 2个生产者,2个消费者 + pthread_create(&tid[0], NULL, consumer, (void*)0); + pthread_create(&tid[1], NULL, producer, (void*)1); + pthread_create(&tid[2], NULL, consumer, (void*)2); + pthread_create(&tid[3], NULL, producer, (void*)3); + + // 输入q或Q结束进程 + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < 4; i++) { + pthread_cancel(tid[i]); + } + break; + } + } + + // 释放信号量 + sem_destroy(&mutex); + sem_destroy(&empty); + sem_destroy(&full); + return 0; +} +``` + +### 2.1.2. PhD + +```c +# include +# include +# include +# include + +// mutex互斥地取筷子 +sem_t chopstick[5]; + +// ===添加的代码=== +sem_t philosopher; +// 设置额外的信号量,用于限制最多只有四位哲学家同时拿起筷子,避免死锁 +// ===添加的代码=== + +void* eat_think(void* arg) { + + // ===修改的代码=== + // int phi = (int)arg; + // ************** + int phi = (long)arg; + // ===修改的代码=== + + while (1) { + + // ===添加的代码=== + sem_wait(&philosopher); + // 哲学家线程首先尝试获取philosopher信号量,如果信号量的值大于0,表示有资源可以使用,可以继续尝试获取筷子。 + // ===添加的代码=== + + // ===修改的代码=== + // sem_wait(&chopstick[phi]); // 拿左 + // sem_wait(&chopstick[(phi + 1) % 5]); // 拿右 + // printf("Philosopher %d fetches chopstick %d\n", phi, phi); + // printf("Philosopher %d fetches chopstick %d\n", phi, phi + 1); + // ************** + sem_wait(&chopstick[phi]); // 拿左 + printf("Philosopher %d fetches chopstick %d\n", phi, phi); // 先输出提示信息 + sem_wait(&chopstick[(phi + 1) % 5]); // 拿右 + printf("Philosopher %d fetches chopstick %d\n", phi, phi + 1); + // 哲学家在尝试获取左边和右边的筷子时,分别对应chopstick[phi]和chopstick[(phi + 1) % 5]。 + // 如果两把筷子都能获取到(chopstick[phi]和chopstick[(phi + 1) % 5]的值为1),则表示可以进餐。 + // ===修改的代码=== + + // ===修改的代码=== + // sleep(5); // 吃饭 + // printf("Philosopher %d is eating...\n", phi); + // ************** + printf("Philosopher %d is eating...\n", phi); // 先输出状态提示 + sleep(5); // 吃饭 + // ===修改的代码=== + + sem_post(&chopstick[phi]); + sem_post(&chopstick[(phi + 1) % 5]); + // 吃完后,哲学家释放左右两边的筷子(分别对应chopstick[phi]和chopstick[(phi + 1) % 5]的值为1) + + printf("Philosopher %d release chopstick %d\n", phi, phi); + printf("Philosopher %d release chopstick %d\n", phi, phi + 1); + + // ===添加的代码=== + sem_post(&philosopher); + // 释放philosopher信号量,表示自己不再占用资源 + // ===添加的代码=== + + // 思考 + sleep(3); + } +} + +int main() { + // 线程id + pthread_t tid[5]; + for (int i = 0; i < 5; ++i) { + sem_init(&chopstick[i], 0, 1); + } + // 数组的每个元素表示一把筷子,初始值为1。哲学家在进餐时需要先获取左边的筷子,再获取右边的筷子,避免死锁。 + + // ===添加的代码=== + sem_init(&philosopher, 0, 4); + // 信号量的初始值为4,表示最多有4位哲学家可以同时尝试获取筷子。 + // ===添加的代码=== + + for (int i = 0; i < 5; ++i) { + pthread_create(&tid[i], NULL, eat_think, (void*)i); + } + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < 4; ++i) { + pthread_cancel(tid[i]); + } + break; + } + + // ===添加的代码=== + else if (c == 'l' || c == 'L') { + int chop_vals[5]; + for (int i = 0; i < 5; ++i) { + sem_getvalue(&chopstick[i], &chop_vals[i]); + } + printf("chops sem_t value: [%d, %d, %d, %d, %d]\n", + chop_vals[0], chop_vals[1], chop_vals[2], chop_vals[3], chop_vals[4]); + } + // 设置一个接口,按"l"或"L"获取筷子状态 + // ===添加的代码=== + + } + for (int i = 0; i < 5; ++i) { + sem_destroy(&chopstick[i]); + } + + // ===添加的代码=== + sem_destroy(&philosopher); + // ===添加的代码=== + + return 0; +} +``` + +### 2.1.3. ReaderWriter + +```c +# include +# include +# include +# include + +// 互斥信号量 wmutex 用于实现对文件的互斥访问 +// rmutex 用于对readCount变量的互斥访问 +sem_t wmutex, rmutex; +// wmutex信号量用于对文件的读写互斥、写写互斥访问 +// rmutex信号量用于对readCount变量的互斥访问,确保多个读者不会同时修改readCount + +// ===临界资源=== +int readCount = 0; +// 记录当前有几个读进程在访问文件 +// ===临界资源=== + +// 读者 +void* Reader(void* param) { + long threadid = (long)param; //进程id + while (1) { + sem_wait(&rmutex); + // 读者线程在读取文件前,首先获取rmutex信号量,确保在读取readCount变量时没有其他读者在修改它 + if (readCount == 0) { //第一个读者到来 + sem_wait(&wmutex); + // 如果是第一个读者到来,读者会获取wmutex信号量,确保没有写者在写文件 + // 这防止了在有写者时有新的读者加入,保证了读写互斥 + } + readCount++; + sem_post(&rmutex); + // 访问临界资源结束,释放rmutex信号量 + printf("Thread-%ld: is reading...\n", threadid); + sleep(1); + printf("Thread-%ld: ends reading.\n", threadid); + + sem_wait(&rmutex); + // 需要访问临界资源readCount,再次获取rmutex信号量 + readCount--; + if (readCount == 0) { //所有读者读完,释放写者 + sem_post(&wmutex); + // 如果是最后一个读者离开,读者会释放wmutex信号量,允许写者访问文件 + } + sem_post(&rmutex); + // 访问临界资源结束,释放rmutex信号量 + } +} + +// 写者 +void* Writer(void* param) { + long threadid = (long)param; + while (1) { + sem_wait(&wmutex); + // 写者线程在写文件前,首先获取wmutex信号量,确保没有其他读者或写者在访问文件。 + printf("Thread-%ld: is writing...\n", threadid); + sleep(2); + printf("Thread-%ld: ends writing.\n", threadid); + sem_post(&wmutex); + // 写者写完文件后,释放wmutex信号量,允许其他读者或写者访问文件。 + } +} + +int main() { + // 信号量初始化 + sem_init(&rmutex, 0, 1); + sem_init(&wmutex, 0, 1); + pthread_t tid[4]; + // 两个读者、两个写者 + pthread_create(&tid[0], NULL, Reader, (void*)0); + pthread_create(&tid[1], NULL, Writer, (void*)1); + pthread_create(&tid[2], NULL, Reader, (void*)2); + pthread_create(&tid[3], NULL, Writer, (void*)3); + //输入q或Q退出 + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < 4; ++i) { + pthread_cancel(tid[i]); + } + break; + } + } + // 释放信号量 + sem_destroy(&rmutex); + sem_destroy(&wmutex); + return 0; +} + +``` + +## 2.2. 测试生产者-消费者代码 + +首先由于Deepin中`gcc (Uos 8.3.0.3-3+rebuild) 8.3.0`的特性,使用`# include `需要在编译时添加参数`-pthread`(这在上一次实验报告中提到)。因此为了方便,我直接将`-pthread`参数与`gcc`绑定,即运行以下命令: + +```sh +alias gcc="gcc -pthread" +``` + +接下来对代码进行测试。 + +### 2.2.1. 一个生产者一个消费者 + +在`main`函数中将生产者和消费者分别设置1个线程。 + +线程1为生产者,线程0为消费者。 + +```c +// 1个生产者,1个消费者 +pthread_create(&tid[0], NULL, consumer, (void*)0); +pthread_create(&tid[1], NULL, producer, (void*)1); +``` + +编译运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./producer-consumer.c -o producer-consumer +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer +Thread-1 : Producer produce product 0 +Thread-1 : Producer produce product 1 +Thread-1 : Producer produce product 2 +Thread-1 : Producer produce product 3 +Thread-1 : Producer produce product 4 +Thread-0 : Consumer consume product 0 +Thread-0 : Consumer consume product 1 +Thread-0 : Consumer consume product 2 +Thread-0 : Consumer consume product 3 +Thread-0 : Consumer consume product 4 +Thread-1 : Producer produce product 5 +Thread-1 : Producer produce product 6 +q +``` + +可以看到,生产者线程和消费者线程的行为交替执行,能正确同步。 + +1. 生产者线程1生产了5个数据`[0, 1, 2, 3, 4]`,消费者线程0等待生产者线程生产完数据; + +2. 消费者进程0消费了5个数据`[0, 1, 2, 3, 4]`,生产者进程1等待消费者线程消费完数据; + +3. 生产者线程1又生产了2个数据`[5, 6]`,消费者线程0等待生产者线程生产完数据; +4. 按q销毁线程退出 + +### 2.2.2. 多个生产者多个消费者 + +在`main`函数中将生产者和消费者分别设置2个线程。 + +线程1、3为生产者,线程0、2为消费者。 + +```c +// 2个生产者,2个消费者 +pthread_create(&tid[0], NULL, consumer, (void*)0); +pthread_create(&tid[1], NULL, producer, (void*)1); +pthread_create(&tid[2], NULL, consumer, (void*)2); +pthread_create(&tid[3], NULL, producer, (void*)3); +``` + +编译运行。 + +```shell +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./producer-consumer.c -o producer-consumer +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer +Thread-1 : Producer produce product 0 +Thread-1 : Producer produce product 1 +Thread-1 : Producer produce product 2 +Thread-1 : Producer produce product 3 +Thread-0 : Consumer consume product 0 +Thread-0 : Consumer consume product 1 +Thread-0 : Consumer consume product 2 +Thread-1 : Producer produce product 4 +Thread-1 : Producer produce product 5 +Thread-1 : Producer produce product 6 +Thread-0 : Consumer consume product 3 +Thread-0 : Consumer consume product 4 +Thread-0 : Consumer consume product 5 +Thread-2 : Consumer consume product 6 +Thread-1 : Producer produce product 7 +q +``` + +输出结果表明,程序中多个生产者多个消费者都能正确同步。 + +1. 生产者线程1生产了4个数据`[0, 1, 2, 3]`,生产者线程3互斥等待,消费者线程0、2等待生产者线程生产完数据; + +2. 消费者进程0消费了3个数据`[0, 1, 2]`,消费者进程2互斥等待,生产者进程1、3等待消费者线程消费完数据; + +3. 生产者线程1又生产了3个数据`[4, 5, 6]`,生产者线程3互斥等待,消费者线程0、2等待生产者线程生产完数据; + +4. 消费者进程0消费了3个数据`[3, 4, 5]`,消费者进程2互斥等待,生产者进程1、3等待消费者线程消费完数据; + +5. 消费者进程2消费了1个数据`[6]`,消费者进程0互斥等待,生产者进程1、3等待消费者线程消费完数据; +6. 生产者线程1生产1个数据`[7]`,生产者线程3互斥等待,消费者线程0、2等待生产者线程生产完数据; +7. 按q销毁线程退出 + +### 2.2.3. 测试去掉同步操作命令 + +#### 2.2.3.1. 测试去掉互斥访问同步信号量mutex + +在源代码中注释掉信号量`mutex`,只保留信号量`empty`和`full`。 + +编译运行。 + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./producer-consumer.c -o producer-consumer +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer +Thread-3 : Producer produce product 0 +Thread-1 : Producer produce product 0 +Thread-0 : Consumer consume product 0 +Thread-2 : Consumer consume product 0 +q +``` + +发现临界资源`id`没有被保护互斥访问。 + +生产者线程1和3都对同一个缓冲区生产了`id`为0的数据;消费者线程0和2都从同一个缓冲区消费了`id`为0的数据。 + +#### 2.2.3.2. 测试去掉资源同步信号量empty、full + +在源代码中注释掉信号量`empty`和`full`,只保留信号量`mutex`。 + +修改`buffer`的初始化代码为: + +```c +int buffer[n] = { 4, 3, 2, 1, 0 }; +``` + +以观察消费者访问缓冲区的情况。 + +编译运行。 + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./producer-consumer.c -o producer-consumer +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer +Thread-0 : Consumer consume product 4 +Thread-0 : Consumer consume product 3 +Thread-0 : Consumer consume product 2 +Thread-0 : Consumer consume product 1 +Thread-0 : Consumer consume product 0 +Thread-0 : Consumer consume product 4 +Thread-0 : Consumer consume product 3 +q +``` + +发现临界资源`buffer`即缓冲区没有被保护互斥访问。 + +没有`full`信号量的保护,在生产者生产数据前,消费者线程2就已经开始访问缓冲区获取数据,即消费数据量超出了生产的数据量。 + +同理,没有`empty`信号量的保护,生产者生产的数据也会超出缓冲区的大小,覆盖掉之前生产且没有被消费者消费掉的数据。 + +### 2.2.4. 测试将进入临界区的两个wait操作位置互换 + +在源代码中将进入临界区的两个wait操作位置互换。 + +为了更好观察信号量情况,在`main`函数`if (c == 'q' || c == 'Q')`后加一个`else if (c == 'l' || c == 'L')`用于创建获取信号量状态的接口。 + +```c +else if (c == 'l' || c == 'L') { + int mutex_value, full_value, empty_value; + sem_getvalue(&mutex, &mutex_value); + sem_getvalue(&full, &full_value); + sem_getvalue(&empty, &empty_value); + printf("mutex=%d, full=%d, empty=%d\n", + mutex_value, full_value, empty_value); +} +``` + +编译后多次测试,出现如下两种情况: + +1. 消费者无法进行消费 + + ```sh + (base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer + Thread-3 : Producer produce product 0 + Thread-3 : Producer produce product 1 + Thread-3 : Producer produce product 2 + Thread-3 : Producer produce product 3 + Thread-3 : Producer produce product 4 + l + mutex=0, full=5, empty=0 + q + ``` + + 观察到生产者将缓冲区填满后,消费者无法消费。 + + 此时信号量`empty`为0,生产者无法再进行生产,但此时信号量`mutex`也为0,生产者和消费者都不能进入临界区。 + + 所以发生了死锁。 + +2. 生产者无法进行生产 + + ```sh + (base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./producer-consumer + l + mutex=0, full=0, empty=5 + q + ``` + + 观察到程序一开始运行,生产者就没有进行生产。 + + 此时信号量`mutex`为0,生产者和消费者都不能进入临界区。 + + 在程序运行过程中: + + 1. `mutex`信号量首先被消费者修改,然后消费者访问`full`信号量,此时`full`为0,没有可消费的资源,无法进入临界区。 + 2. 但此时`mutex`信号量为0,生产者也无法进入临界区。 + + 发生了死锁。 + +## 2.3. 测试哲学家进餐问题代码 + +仅在原代码中作如下修改: + +- 添加输出信号量的功能代码,输入“l”或“L”打印筷子信号量状态; +- 没拿起一支筷子就输出提示信息,而不是拿起两只筷子后才输出提示信息。 + +不添加进餐哲学家信号量。 + +编译运行。 + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./PhD.c -o ./PhD +./PhD.c: In function ‘main’: +./PhD.c:88:50: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] + pthread_create(&tid[i], NULL, eat_think, (void*)i); + ^ +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./PhD +Philosopher 1 fetches chopstick 1 +Philosopher 4 fetches chopstick 4 +Philosopher 0 fetches chopstick 0 +Philosopher 3 fetches chopstick 3 +Philosopher 2 fetches chopstick 2 +l +chops sem_t value: [0, 0, 0, 0, 0] +q +``` + +发生死锁。 + +原因很明显,哲学家`[1, 4, 0, 3, 2]`各自拿起了其左手边的筷子,而右手边的筷子被其他哲学家占有了,所以五位哲学家都无法进餐。 + +### 2.3.1. 修改方案1 + +添加哲学家信号量,限定持有筷子的哲学家数小于5。 + +修改代码,最终代码如**2.1.2. PhD**代码块中代码所示。该代码能避免死锁,使程序正常运行。 + +### 2.3.2. 修改方案2 + +使用mutex互斥信号量,一次取够一双筷子。 + +此时将筷子信号量作为临界资源,一次只允许一个哲学家线程修改。 + +修改代码,删去哲学家信号量,添加`mutex`信号量,将拿取筷子的行为代码包在`wait(mutex)`和`signal(mutex)`内。 + +```c +sem_t mutex; +// 设置额外的信号量,用于互斥拿起两只筷子的行为 + +void* eat_think(void* arg) { + int phi = (long)arg; + while (1) { + sem_wait(&mutex); + sem_wait(&chopstick[phi]); // 拿左 + printf("Philosopher %d fetches chopstick %d\n", phi, phi); + sem_wait(&chopstick[(phi + 1) % 5]); // 拿右 + printf("Philosopher %d fetches chopstick %d\n", phi, phi + 1); + sem_post(&mutex); + + printf("Philosopher %d is eating...\n", phi); + sleep(5); // 进餐 + sem_post(&chopstick[phi]); + sem_post(&chopstick[(phi + 1) % 5]); + printf("Philosopher %d release chopstick %d\n", phi, phi); + printf("Philosopher %d release chopstick %d\n", phi, phi + 1); + sleep(3); // 思考 + } +} +``` + +编译运行:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./PhD_1.c -o ./PhD_1 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./PhD_1 +Philosopher 2 fetches chopstick 2 +Philosopher 2 fetches chopstick 3 +Philosopher 2 is eating... +Philosopher 1 fetches chopstick 1 +l +chops sem_t value: [1, 0, 0, 0, 1] +Philosopher 2 release chopstick 2 +Philosopher 2 release chopstick 3 +Philosopher 1 fetches chopstick 2 +Philosopher 1 is eating... +Philosopher 3 fetches chopstick 3 +Philosopher 3 fetches chopstick 4 +Philosopher 3 is eating... +l +chops sem_t value: [1, 0, 0, 0, 0] +Philosopher 1 release chopstick 1 +Philosopher 1 release chopstick 2 +Philosopher 3 release chopstick 3 +Philosopher 3 release chopstick 4 +Philosopher 4 fetches chopstick 4 +Philosopher 4 fetches chopstick 5 +Philosopher 4 is eating... +l +chops sem_t value: [0, 1, 1, 1, 0] +q +``` + +没有发生死锁,程序正常运行。 + +### 2.3.3. 修改方案3 + +编号为奇数的哲学家先尝试拿左手边的筷子再尝试拿右手边的筷子;编号为偶数的哲学家先尝试拿右手边的筷子再尝试拿左手边的筷子。 + +这样就不需要添加额外的信号量了。 + +修改代码,删去`mutex`信号量,定义先后拿筷子的编号: + +- `phi`为奇时,`(phi + 1) % 2`为0,`first`为`phi`;`phi % 2`为1,`second`为`(phi + 1) % 5`; +- `phi`为偶时,`(phi + 1) % 2`为1,`first`为`(phi + 1) % 5`;`phi % 2`为0,`second`为`phi`; + +```c +void* eat_think(void* arg) { + int phi = (long)arg; + while (1) { + int first = (phi + ((phi + 1) % 2)) % 5, second = (phi + (phi % 2)) % 5; + sem_wait(&chopstick[first]); + printf("Philosopher %d fetches chopstick %d\n", phi, first); + sem_wait(&chopstick[second]); + printf("Philosopher %d fetches chopstick %d\n", phi, second); + + printf("Philosopher %d is eating...\n", phi); + sleep(5); // 吃饭 + sem_post(&chopstick[phi]); + sem_post(&chopstick[(phi + 1) % 5]); + printf("Philosopher %d release chopstick %d\n", phi, phi); + printf("Philosopher %d release chopstick %d\n", phi, phi + 1); + sleep(3); // 思考 + } +} +``` + +编译运行:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./PhD_2.c -o ./PhD_2 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./PhD_2 +Philosopher 2 fetches chopstick 3 +Philosopher 2 fetches chopstick 2 +Philosopher 2 is eating... +Philosopher 4 fetches chopstick 0 +Philosopher 4 fetches chopstick 4 +Philosopher 4 is eating... +Philosopher 0 fetches chopstick 1 +l +chops sem_t value: [0, 0, 0, 0, 0] +Philosopher 2 release chopstick 2 +Philosopher 2 release chopstick 3 +Philosopher 3 fetches chopstick 3 +Philosopher 3 fetches chopstick 4 +Philosopher 3 is eating... +Philosopher 4 release chopstick 4 +Philosopher 4 release chopstick 5 +Philosopher 0 fetches chopstick 0 +Philosopher 0 is eating... +l +chops sem_t value: [0, 0, 1, 0, 0] +q +``` + +没有发生死锁,程序正常运行。 + +## 2.4. 测试读者写者问题 + +### 2.4.1. 测试ReaderWriter.c原代码 + +按照原代码(仅更改:在读写行为结束后输出提示信息),编译运行,出现两种结果。 + +1. 读者线程一直挤占互斥信号量,使得写者线程无法进入临界区。 + + ```sh + (base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter.c -o ./ReaderWriter + (base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter + Thread-0: is reading... + Thread-2: is reading... + Thread-0: ends reading. + Thread-0: is reading... + Thread-2: ends reading. + Thread-2: is reading... + Thread-0: ends reading. + Thread-0: is reading... + Thread-2: ends reading. + Thread-2: is reading... + q + ``` + +2. 写者线程一直挤占互斥信号量,使得读者线程无法进入临界区。 + + ```sh + (base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter + Thread-1: is writing... + Thread-1: ends writing. + Thread-1: is writing... + Thread-1: ends writing. + Thread-1: is writing... + Thread-1: ends writing. + Thread-1: is writing... + q + ``` + +出现上述问题的原因是读写行为过于频繁。修改原代码,在每次读写行为结束后`sleep(2)`以减少读写行为频率。 + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter.c -o ./ReaderWriter +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter +Thread-0: is reading... +Thread-2: is reading... +Thread-0: ends reading. +Thread-2: ends reading. +Thread-1: is writing... +Thread-1: ends writing. +Thread-3: is writing... +q +``` + +从以上输出可见,读写顺序正常了。 + +### 2.4.2. 构造读写者执行序列测试 + +修改头文件等引用,以精确获取当前时间,如下: + +```c +# include +typedef struct timeval tv; +``` + +修改读者函数,去掉`while()`循环,每个线程仅读操作一次,如下: + +```c +// 读者 +void* Reader(void* param) { + long threadid = (long)param; + tv reach_t; + gettimeofday(&reach_t, NULL); + printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); + sem_wait(&rmutex); + if (readCount == 0) { + sem_wait(&wmutex); + } + tv exec_t; + gettimeofday(&exec_t, NULL); + printf("Thread-%ld starts executing at time %ld, waited %ldus\n", threadid, exec_t.tv_usec, exec_t.tv_usec - reach_t.tv_usec); + readCount++; + sem_post(&rmutex); + + printf("Thread-%ld: is reading...\n", threadid); + sleep(1); + printf("Thread-%ld: ends reading.\n", threadid); + + sem_wait(&rmutex); + readCount--; + if (readCount == 0) { + sem_post(&wmutex); + } + sem_post(&rmutex); +} +``` + +修改写者函数,去掉`while()`循环,每个线程仅写操作一次,如下: + +```c +// 写者 +void* Writer(void* param) { + long threadid = (long)param; + tv reach_t; + gettimeofday(&reach_t, NULL); + printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); + sem_wait(&wmutex); + tv exec_t; + gettimeofday(&exec_t, NULL); + printf("Thread-%ld starts executing at time %ld, waited %ldus\n", threadid, exec_t.tv_usec, exec_t.tv_usec - reach_t.tv_usec); + printf("Thread-%ld: is writing...\n", threadid); + sleep(2); + printf("Thread-%ld: ends writing.\n", threadid); + sem_post(&wmutex); +} +``` + +#### 2.4.2.1. 序列1测试 + +首先执行1个写者线程,然后执行9个读者线程。 + +`main`函数设计如下: + +```c +int main() { + sem_init(&rmutex, 0, 1); + sem_init(&wmutex, 0, 1); + pthread_t tid[10]; + int num_pthread = 10; + pthread_create(&tid[0], NULL, Writer, (void*)0); + for (int i = 1; i < num_pthread; ++i) { + pthread_create(&tid[i], NULL, Reader, (void*)i); + } + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < num_pthread; ++i) { + pthread_cancel(tid[i]); + } + break; + } + } + sem_destroy(&rmutex); + sem_destroy(&wmutex); + return 0; +} +``` + +编译运行如下: + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_1.c -o ./ReaderWriter_1 +./ReaderWriter_1.c: In function ‘main’: +./ReaderWriter_1.c:64:41: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] + pthread_create(&tid[i], NULL, Reader, (void*)i); + ^ +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ # first execution +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-1 reached at time 381669 +Thread-1 starts executing at time 381726, waited 57us +Thread-5 reached at time 381729 +Thread-5 starts executing at time 381736, waited 7us +Thread-5: is reading... +Thread-6 reached at time 381740 +Thread-6 starts executing at time 381747, waited 7us +Thread-6: is reading... +Thread-1: is reading... +Thread-4 reached at time 381713 +Thread-0 reached at time 381672 +Thread-9 reached at time 381780 +Thread-3 reached at time 381681 +Thread-7 reached at time 381757 +Thread-8 reached at time 381765 +Thread-4 starts executing at time 381782, waited 69us +Thread-4: is reading... +Thread-9 starts executing at time 381808, waited 28us +Thread-9: is reading... +Thread-3 starts executing at time 381811, waited 130us +Thread-8 starts executing at time 381813, waited 48us +Thread-8: is reading... +Thread-3: is reading... +Thread-7 starts executing at time 381817, waited 60us +Thread-7: is reading... +Thread-2 reached at time 381751 +Thread-2 starts executing at time 381845, waited 94us +Thread-2: is reading... +Thread-5: ends reading. +Thread-1: ends reading. +Thread-7: ends reading. +Thread-3: ends reading. +Thread-8: ends reading. +Thread-9: ends reading. +Thread-6: ends reading. +Thread-4: ends reading. +Thread-2: ends reading. +Thread-0 starts executing at time 381937, waited 265us +Thread-0: is writing... +Thread-0: ends writing. +q +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ # second execution +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-0 reached at time 742894 +Thread-0 starts executing at time 742941, waited 47us +Thread-0: is writing... +Thread-1 reached at time 742901 +Thread-2 reached at time 742930 +Thread-3 reached at time 742961 +Thread-4 reached at time 742982 +Thread-5 reached at time 743000 +Thread-6 reached at time 743017 +Thread-7 reached at time 743031 +Thread-8 reached at time 743046 +Thread-9 reached at time 743061 +Thread-0: ends writing. +Thread-1 starts executing at time 743130, waited 229us +Thread-1: is reading... +Thread-2 starts executing at time 743144, waited 214us +Thread-2: is reading... +Thread-3 starts executing at time 743153, waited 192us +Thread-3: is reading... +Thread-4 starts executing at time 743161, waited 179us +Thread-4: is reading... +Thread-5 starts executing at time 743171, waited 171us +Thread-5: is reading... +Thread-6 starts executing at time 743182, waited 165us +Thread-6: is reading... +Thread-7 starts executing at time 743194, waited 163us +Thread-7: is reading... +Thread-8 starts executing at time 743202, waited 156us +Thread-8: is reading... +Thread-9 starts executing at time 743210, waited 149us +Thread-9: is reading... +Thread-3: ends reading. +Thread-7: ends reading. +Thread-1: ends reading. +Thread-5: ends reading. +Thread-2: ends reading. +Thread-8: ends reading. +Thread-4: ends reading. +Thread-9: ends reading. +Thread-6: ends reading. +q +``` + +可见: + +- 当读者线程比写者线程先占用了`mutex`互斥信号量,那么写者就会在所有读者线程结束后最后执行临界区,如第1次执行结果所示; +- 如果写者线程比读者线程先占用了`mutex`互斥信号量,那么读者进程会在该写者进程结束后执行,如第2次执行结果所示。 + +即:读者优先。 + +在读者线程之间,执行顺序也不是按照线程创建函数`pthread_create()`的执行顺序执行的。线程的创建、过程执行顺序都是随机的。例证如下: + +在第1次执行过程中,第21行`Thread-7 reached at time 381757`和第22行`Thread-8 reached at time 381765`显示第7个线程(读者)比第8个线程(读者)到达时间早,但是第28行`Thread-8 starts executing at time 381813, waited 48us`和第31行`Thread-7 starts executing at time 381817, waited 60us`显示第7个线程(读者)比第8个线程(读者)执行时间晚。猜测是因为执行顺序如下: + +1. [Thread-7] printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); +2. [Thread-8] printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); +3. [Thread-8] sem_wait(&rmutex); +4. [Thread-7] sem_wait(&rmutex); + +#### 2.4.2.2. 序列2测试 + +首先执行1个读者线程,然后执行9个写者线程。 + +`main`函数中`pthread_create()`部分设计如下: + +```c +pthread_create(&tid[0], NULL, Reader, (void*)0); +for (int i = 1; i < num_pthread; ++i) { + pthread_create(&tid[i], NULL, Writer, (void*)i); +} +``` + +编译运行如下:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_1.c -o ./ReaderWriter_1 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-1 reached at time 591009 +Thread-0 reached at time 591009 +Thread-2 reached at time 591026 +Thread-3 reached at time 591041 +Thread-4 reached at time 591051 +Thread-1 starts executing at time 591055, waited 46us +Thread-7 reached at time 591084 +Thread-6 reached at time 591072 +Thread-1: is writing... +Thread-5 reached at time 591061 +Thread-8 reached at time 591095 +Thread-9 reached at time 591107 +Thread-1: ends writing. +Thread-0 starts executing at time 591371, waited 362us +Thread-0: is reading... +Thread-0: ends reading. +Thread-2 starts executing at time 591553, waited 527us +Thread-2: is writing... +Thread-2: ends writing. +Thread-3 starts executing at time 591650, waited 609us +Thread-3: is writing... +Thread-3: ends writing. +Thread-4 starts executing at time 591789, waited 738us +Thread-4: is writing... +Thread-4: ends writing. +Thread-7 starts executing at time 591876, waited 792us +Thread-7: is writing... +Thread-7: ends writing. +Thread-6 starts executing at time 592060, waited 988us +Thread-6: is writing... +Thread-6: ends writing. +Thread-5 starts executing at time 592292, waited 1231us +Thread-5: is writing... +Thread-5: ends writing. +Thread-8 starts executing at time 592461, waited 1366us +Thread-8: is writing... +Thread-8: ends writing. +Thread-9 starts executing at time 592694, waited 1587us +Thread-9: is writing... +Thread-9: ends writing. +q +``` + +可见,只要有读者线程尝试执行,就一定会在当前写者线程进行完之后立即执行,即读者优先。 + +#### 2.4.2.3. 序列3测试 + +交替执行5个写者线程和5个读者线程。 + +`main`函数中`pthread_create()`部分设计如下: + +```c +for (int i = 0; i < num_pthread / 2; ++i) { + pthread_create(&tid[2 * i], NULL, Writer, (void*)(2 * i)); + pthread_create(&tid[2 * i + 1], NULL, Reader, (void*)(2 * i + 1)); +} +``` + +编译运行如下:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_1.c -o ./ReaderWriter_1 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-1 reached at time 338262 +Thread-5 reached at time 338314 +Thread-6 reached at time 338327 +Thread-3 reached at time 338262 +Thread-4 reached at time 338280 +Thread-0 reached at time 338307 +Thread-2 reached at time 338268 +Thread-1 starts executing at time 338314, waited 52us +Thread-1: is reading... +Thread-8 reached at time 338353 +Thread-7 reached at time 338340 +Thread-5 starts executing at time 338360, waited 46us +Thread-5: is reading... +Thread-9 reached at time 338365 +Thread-3 starts executing at time 338381, waited 119us +Thread-3: is reading... +Thread-7 starts executing at time 338397, waited 57us +Thread-7: is reading... +Thread-9 starts executing at time 338410, waited 45us +Thread-9: is reading... +Thread-1: ends reading. +Thread-9: ends reading. +Thread-5: ends reading. +Thread-7: ends reading. +Thread-3: ends reading. +Thread-6 starts executing at time 338589, waited 262us +Thread-6: is writing... +Thread-6: ends writing. +Thread-4 starts executing at time 338797, waited 517us +Thread-4: is writing... +Thread-4: ends writing. +Thread-0 starts executing at time 339102, waited 795us +Thread-0: is writing... +Thread-0: ends writing. +Thread-2 starts executing at time 339415, waited 1147us +Thread-2: is writing... +Thread-2: ends writing. +Thread-8 starts executing at time 339989, waited 1636us +Thread-8: is writing... +Thread-8: ends writing. +q +``` + +可见执行顺序还是:只要有读者线程尝试执行,就一定会在当前写者线程进行完之后立即执行,即读者优先。 + +#### 2.4.2.4. 序列4测试 + +执行10个写者线程。 + +`main`函数中`pthread_create()`部分设计如下: + +```c +for (int i = 0; i < num_pthread; ++i) { + pthread_create(&tid[i], NULL, Writer, (void*)i); +} +``` + +编译运行如下:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_1.c -o ./ReaderWriter_1 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-4 reached at time 493308 +Thread-4 starts executing at time 493362, waited 54us +Thread-4: is writing... +Thread-3 reached at time 493302 +Thread-8 reached at time 493379 +Thread-1 reached at time 493390 +Thread-5 reached at time 493334 +Thread-6 reached at time 493353 +Thread-0 reached at time 493307 +Thread-7 reached at time 493367 +Thread-9 reached at time 493391 +Thread-2 reached at time 493331 +Thread-4: ends writing. +Thread-3 starts executing at time 493608, waited 306us +Thread-3: is writing... +Thread-3: ends writing. +Thread-8 starts executing at time 493828, waited 449us +Thread-8: is writing... +Thread-8: ends writing. +Thread-1 starts executing at time 494108, waited 718us +Thread-1: is writing... +Thread-1: ends writing. +Thread-5 starts executing at time 494346, waited 1012us +Thread-5: is writing... +Thread-5: ends writing. +Thread-6 starts executing at time 494664, waited 1311us +Thread-6: is writing... +Thread-6: ends writing. +Thread-7 starts executing at time 495280, waited 1913us +Thread-7: is writing... +Thread-7: ends writing. +Thread-9 starts executing at time 495533, waited 2142us +Thread-9: is writing... +Thread-9: ends writing. +Thread-0 starts executing at time 495772, waited 2465us +Thread-0: is writing... +Thread-0: ends writing. +Thread-2 starts executing at time 496053, waited 2722us +Thread-2: is writing... +Thread-2: ends writing. +q +``` + +写写互斥,同步机制没有出现问题。 + +#### 2.4.2.5. 序列5测试 + +执行10个读者线程。 + +`main`函数中`pthread_create()`部分设计如下: + +```c +for (int i = 0; i < num_pthread; ++i) { + pthread_create(&tid[i], NULL, Reader, (void*)i); +} +``` + +编译运行如下:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_1.c -o ./ReaderWriter_1 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_1 +Thread-1 reached at time 875992 +Thread-1 starts executing at time 876030, waited 38us +Thread-1: is reading... +Thread-4 reached at time 876047 +Thread-4 starts executing at time 876054, waited 7us +Thread-4: is reading... +Thread-2 reached at time 876011 +Thread-2 starts executing at time 876068, waited 57us +Thread-2: is reading... +Thread-5 reached at time 876059 +Thread-5 starts executing at time 876076, waited 17us +Thread-5: is reading... +Thread-3 reached at time 876030 +Thread-3 starts executing at time 876088, waited 58us +Thread-3: is reading... +Thread-0 reached at time 875991 +Thread-9 reached at time 876102 +Thread-6 reached at time 876069 +Thread-0 starts executing at time 876102, waited 111us +Thread-7 reached at time 876080 +Thread-8 reached at time 876091 +Thread-0: is reading... +Thread-9 starts executing at time 876118, waited 16us +Thread-9: is reading... +Thread-6 starts executing at time 876142, waited 73us +Thread-6: is reading... +Thread-7 starts executing at time 876149, waited 69us +Thread-7: is reading... +Thread-8 starts executing at time 876158, waited 67us +Thread-8: is reading... +Thread-2: ends reading. +Thread-1: ends reading. +Thread-5: ends reading. +Thread-3: ends reading. +Thread-4: ends reading. +Thread-0: ends reading. +Thread-9: ends reading. +Thread-6: ends reading. +Thread-7: ends reading. +Thread-8: ends reading. +q +``` + +读读不互斥,读者到达即读,程序正常运行。 + +## 2.5. 实现并测试写者优先的读者写者算法 + +### 2.5.1. 算法实现 + +算法逻辑写在注释中: + +```c +# include +# include +# include +# include + +sem_t wmutex, rmutex, wmutex_1, rmutex_1; +// wmutex信号量用于对文件的读写互斥、写写互斥访问 +// rmutex信号量用于对readCount变量的互斥访问,确保多个读者不会同时修改readCount +// wmutex_1信号量用于对writeCount变量的互斥访问,确保多个写者不会同时修改writeCount +// rmutex_1信号量用于对文件的写者优先的读写互斥 + +int readCount = 0; +// 记录当前有几个读进程在访问文件 +int writeCount = 0; +// 记录当前有几个写进程在访问文件 + +// 读者 +void* Reader(void* param) { + long threadid = (long)param; //进程id + while (1) { + sem_wait(&rmutex_1); + // 首先获取rmutex_1信号量,确保没有写者在访问文件。 + sem_wait(&rmutex); + if (readCount == 0) { + sem_wait(&wmutex); + } + readCount++; + sem_post(&rmutex); + + printf("Thread-%ld: is reading...\n", threadid); + sleep(1); + printf("Thread-%ld: ends reading.\n", threadid); + + sem_wait(&rmutex); + readCount--; + if (readCount == 0) { + sem_post(&wmutex); + } + sem_post(&rmutex); + sem_post(&rmutex_1); // 释放rmutex_1信号量 + sleep(2); + } +} + +// 写者 +void* Writer(void* param) { + long threadid = (long)param; + while (1) { + sem_wait(&wmutex_1); + // 首先获取wmutex_1信号量,确保在读取writeCount变量时没有其他写者在修改它 + if (writeCount == 0) { + sem_wait(&rmutex_1); + // 如果是第一个写者到来,写者会获取rmutex_1信号量,确保没有读者在写文件 + // 这防止了在有读者时有新的写者加入,保证了读写互斥 + } + writeCount++; + sem_post(&wmutex_1); + // 释放wmutex_1信号量 + sem_wait(&wmutex); + + printf("Thread-%ld: is writing...\n", threadid); + sleep(2); + printf("Thread-%ld: ends writing.\n", threadid); + + sem_post(&wmutex); + sem_wait(&wmutex_1); + // 获取wmutex_1信号量,确保在读取writeCount变量时没有其他写者在修改它 + writeCount--; + if (writeCount == 0) { //所有写者写完,释放读者 + sem_post(&rmutex_1); + // 如果是最后一个写者离开,写者会释放rmutex_1信号量,允许读者访问文件 + } + sem_post(&wmutex_1); + // 释放wmutex_1信号量 + sleep(2); + } +} + +int main() { + // 信号量初始化 + sem_init(&rmutex, 0, 1); + sem_init(&wmutex, 0, 1); + sem_init(&rmutex_1, 0, 1); + sem_init(&wmutex_1, 0, 1); + pthread_t tid[4]; + // 两个读者、两个写者 + pthread_create(&tid[0], NULL, Reader, (void*)0); + pthread_create(&tid[1], NULL, Writer, (void*)1); + pthread_create(&tid[2], NULL, Reader, (void*)2); + pthread_create(&tid[3], NULL, Writer, (void*)3); + //输入q或Q退出 + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < 4; ++i) { + pthread_cancel(tid[i]); + } + break; + } + } + // 释放信号量 + sem_destroy(&rmutex); + sem_destroy(&wmutex); + sem_destroy(&rmutex_1); + sem_destroy(&wmutex_1); + return 0; +} +``` + +编译运行: + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_2.c -o ./ReaderWriter_2 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_2 +Thread-3: is writing... +Thread-3: ends writing. +Thread-1: is writing... +Thread-1: ends writing. +Thread-3: is writing... +Thread-3: ends writing. +Thread-0: is reading... +Thread-0: ends reading. +Thread-2: is reading... +Thread-2: ends reading. +Thread-1: is writing... +Thread-1: ends writing. +Thread-3: is writing... +Thread-3: ends writing. +Thread-1: is writing... +q +``` + +看得出来,程序主要由写者掌控。 + +### 2.5.2. 构造执行序列测试 + +我们把这个算法移植到2.4.2.3. 序列3(交替执行5个写者线程和5个读者线程)中,用到达时间和执行时间评价运行效果。 + +代码如下: + +```c +# include +# include +# include +# include +# include + +typedef struct timeval tv; + +sem_t wmutex, rmutex, wmutex_1, rmutex_1; + +int readCount = 0, writeCount = 0; + +// 读者 +void* Reader(void* param) { + long threadid = (long)param; + tv reach_t; + gettimeofday(&reach_t, NULL); + printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); + sem_wait(&rmutex_1); + sem_wait(&rmutex); + if (readCount == 0) { + sem_wait(&wmutex); + } + tv exec_t; + gettimeofday(&exec_t, NULL); + printf("Thread-%ld starts executing at time %ld, waited %ldus\n", threadid, exec_t.tv_usec, exec_t.tv_usec - reach_t.tv_usec); + readCount++; + sem_post(&rmutex); + + printf("Thread-%ld: is reading...\n", threadid); + sleep(1); + printf("Thread-%ld: ends reading.\n", threadid); + + sem_wait(&rmutex); + readCount--; + if (readCount == 0) { + sem_post(&wmutex); + } + sem_post(&rmutex); + sem_post(&rmutex_1); +} + +// 写者 +void* Writer(void* param) { + long threadid = (long)param; + tv reach_t; + gettimeofday(&reach_t, NULL); + printf("Thread-%ld reached at time %ld\n", threadid, reach_t.tv_usec); + sem_wait(&wmutex_1); + if (writeCount == 0) { + sem_wait(&rmutex_1); + } + writeCount++; + sem_post(&wmutex_1); + sem_wait(&wmutex); + tv exec_t; + gettimeofday(&exec_t, NULL); + printf("Thread-%ld starts executing at time %ld, waited %ldus\n", threadid, exec_t.tv_usec, exec_t.tv_usec - reach_t.tv_usec); + printf("Thread-%ld: is writing...\n", threadid); + sleep(2); + printf("Thread-%ld: ends writing.\n", threadid); + sem_post(&wmutex); + sem_wait(&wmutex_1); + writeCount--; + if (writeCount == 0) { + sem_post(&rmutex_1); + } + sem_post(&wmutex_1); +} + +int main() { + sem_init(&rmutex, 0, 1); + sem_init(&wmutex, 0, 1); + sem_init(&rmutex_1, 0, 1); + sem_init(&wmutex_1, 0, 1); + pthread_t tid[10]; + int num_pthread = 10; + for (int i = 0; i < num_pthread / 2; ++i) { + pthread_create(&tid[2 * i], NULL, Reader, (void*)(2 * i)); + pthread_create(&tid[2 * i + 1], NULL, Writer, (void*)(2 * i + 1)); + } + while (1) { + char c = getchar(); + if (c == 'q' || c == 'Q') { + for (int i = 0; i < num_pthread; ++i) { + pthread_cancel(tid[i]); + } + break; + } + } + sem_destroy(&rmutex); + sem_destroy(&wmutex); + sem_destroy(&rmutex_1); + sem_destroy(&wmutex_1); + return 0; +} +``` + +编译运行如下:(省略编译警告信息) + +```sh +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ gcc ./ReaderWriter_3.c -o ./ReaderWriter_3 +(base) kejingfan@KJF-Huawei-PC:~/CodeDir/OS_Lab/Lab2$ ./ReaderWriter_3 +Thread-0 reached at time 325598 +Thread-0 starts executing at time 325635, waited 37us +Thread-0: is reading... +Thread-2 reached at time 325603 +Thread-3 reached at time 325623 +Thread-1 reached at time 325598 +Thread-4 reached at time 325649 +Thread-5 reached at time 325683 +Thread-6 reached at time 325696 +Thread-7 reached at time 325708 +Thread-8 reached at time 325720 +Thread-9 reached at time 325734 +Thread-0: ends reading. +Thread-2 starts executing at time 325720, waited 117us +Thread-2: is reading... +Thread-2: ends reading. +Thread-3 starts executing at time 325844, waited 221us +Thread-3: is writing... +Thread-3: ends writing. +Thread-1 starts executing at time 325923, waited 325us +Thread-1: is writing... +Thread-1: ends writing. +Thread-5 starts executing at time 326187, waited 504us +Thread-5: is writing... +Thread-5: ends writing. +Thread-7 starts executing at time 326272, waited 564us +Thread-7: is writing... +Thread-7: ends writing. +Thread-9 starts executing at time 326365, waited 631us +Thread-9: is writing... +Thread-9: ends writing. +Thread-4 starts executing at time 326564, waited 915us +Thread-4: is reading... +Thread-4: ends reading. +Thread-6 starts executing at time 326878, waited 1182us +Thread-6: is reading... +Thread-6: ends reading. +Thread-8 starts executing at time 327463, waited 1743us +Thread-8: is reading... +Thread-8: ends reading. +q +``` + +执行顺序为: + +1. 读者线程0、2到达 +2. 读者线程0执行 +3. 写者线程3、1到达 +4. 读者线程4到达 +5. …… +6. 读者线程2执行 +7. **写者线程所有线程执行** +8. **读者线程4执行** +9. …… + +总结即为,当有写者线程到达,则将当前读者线程队列执行完毕后立即执行接下来到达的所有写者线程,当所有写者线程结束后,才执行到达的读者线程。 + +写者优先实现了。 + + + +# 3. 问题与讨论 + +本实验我没有遇到特别大的问题。 + + + +# 4. 实验总结 + +- 信号量可以用于对共享资源的互斥访问和线程间的同步通信; +- 在多线程编程中,需要仔细设计同步和互斥的策略,才能写出健壮的多线程程序,否则容易导致死锁等问题。 diff --git a/Lab/Lab2/source/21281280-柯劲帆-第2次实验.pdf b/Lab/Lab2/source/21281280-柯劲帆-第2次实验.pdf new file mode 100644 index 0000000..580aaec Binary files /dev/null and b/Lab/Lab2/source/21281280-柯劲帆-第2次实验.pdf differ diff --git a/Lab/Lab3/21281280-柯劲帆-第3次实验.pdf b/Lab/Lab3/21281280-柯劲帆-第3次实验.pdf new file mode 100644 index 0000000..8c7c99c Binary files /dev/null and b/Lab/Lab3/21281280-柯劲帆-第3次实验.pdf differ diff --git a/Lab/Lab3/requirements/Linux内核完全注释.pdf b/Lab/Lab3/requirements/Linux内核完全注释.pdf new file mode 100644 index 0000000..105e28b Binary files /dev/null and b/Lab/Lab3/requirements/Linux内核完全注释.pdf differ diff --git a/Lab/Lab3/requirements/bochs编译linux-0.11.docx b/Lab/Lab3/requirements/bochs编译linux-0.11.docx new file mode 100644 index 0000000..4ae5738 Binary files /dev/null and b/Lab/Lab3/requirements/bochs编译linux-0.11.docx differ diff --git a/Lab/Lab3/requirements/第3次实验-进程控制实现机制.docx b/Lab/Lab3/requirements/第3次实验-进程控制实现机制.docx new file mode 100644 index 0000000..490a933 Binary files /dev/null and b/Lab/Lab3/requirements/第3次实验-进程控制实现机制.docx differ diff --git a/Lab/Lab3/requirements/第三次实验的示例指导.docx b/Lab/Lab3/requirements/第三次实验的示例指导.docx new file mode 100644 index 0000000..4d93c5f Binary files /dev/null and b/Lab/Lab3/requirements/第三次实验的示例指导.docx differ diff --git a/Lab/Lab3/source/21281280-柯劲帆-第3次实验.md b/Lab/Lab3/source/21281280-柯劲帆-第3次实验.md new file mode 100644 index 0000000..8e6092d --- /dev/null +++ b/Lab/Lab3/source/21281280-柯劲帆-第3次实验.md @@ -0,0 +1,297 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:Linux进程控制的实现机制
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年11月6日
+
+ + +--- + +## 目录 + +[TOC] + +--- + + + +# 1. 开发运行环境和工具 + +- **代码阅读环境**:Windows11 + VS Code +- **开发测试环境**:Bochs + + + +# 2. 实验过程、分析和结论 + +## 2.1. fork系统调用和内核函数 + +首先在`init/main.c`-->`main()`中看到了`fork()`函数 + +```c +if (!fork()) { /* we count on this going ok */ + init(); +} +``` + +即为如果`fork()`没有发现父进程(后面会讲到),则初始化系统,创建系统初始进程。 + +追踪`fork()`,发现在`main.c`开头有一行: + +```c +static inline _syscall0(int,fork) +``` + +即调用`fork()`时,编译器会将`fork()`内嵌为`_syscall0(int,fork)`,即在编译时直接替换为`_syscall0(int,fork)`的函数代码,以免调用堆栈。 + +追踪`_syscall0()`,在`include/unistd.h`中: + +```asm +#define _syscall0(type,name) \ +type name(void) \ +{ \ +long __res; \ +__asm__ volatile ("int $0x80" \ + : "=a" (__res) \ + : "0" (__NR_##name)); \ +if (__res >= 0) \ + return (type) __res; \ +errno = -__res; \ +return -1; \ +} +``` + +即,调用`fork()`就是在调用 + +```asm +"int $0x80" : "=a" (__res) : "0" (__NR_fork) +``` + +- `"int $0x80"`:表示触发软中断。在x86体系结构中,软中断0x80用于进入内核态进行系统调用。 +- `: "=a" (__res)`:对输出操作数的约束。`=a` 表示将寄存器 `eax` 的值分配给 `__res`,并且这个值会在系统调用结束后存储系统调用的返回值。 +- `: "0" (__NR_fork)`:对输入操作数的约束。`0` 表示使用与前面输出操作数相同的寄存器,这里是 `eax`。`__NR_fork` 是系统调用号的宏,被定义为`2`,用于标识调用的具体系统调用。 + +那么调用`fork()`时,就是调用了2号系统软中断。 + +接下来到`kernel/system_call.s`中查看软中断函数`_system_call`代码是如何执行的:首先进行了一系列参数的检查和原寄存器入栈保存操作,这里不予赘述。然后执行了 + +```asm +call _sys_call_table(,%eax,4) +``` + +按照传进来的参数,实际上调用了`_sys_call_table(,2,4)` + +由于这是汇编文件,调用`_sys_call_table()`实际上就是在调用C语言代码中的`sys_call_table()`函数,因为在编译时,编译器会在C代码函数前加上`_`作为其在汇编代码中的命名(实验证明见附录)。 + +那么追踪`sys_call_table()`,在`include/linux/sys.h`中发现了系统调用表: + +```c +fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, +sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, +sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, +sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, +sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, +sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, +sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, +sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, +sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, +sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, +sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, +sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, +sys_setreuid,sys_setregid }; +``` + +其中变量类型`fn_ptr`是函数指针。所以实际上`_sys_call_table(,2,4)`调用了第3个`sys_fork`函数。`sys_fork`函数在汇编后会被命名成`_sys_fork`。 + +我在`kernel/system_call.s`中找到了对其汇编后的调用`_sys_fork`,即系统调用`fork`的函数原型就是: + +```asm +_sys_fork: + call _find_empty_process + testl %eax,%eax + js 1f + push %gs + pushl %esi + pushl %edi + pushl %ebp + pushl %eax + call _copy_process + addl $20,%esp +1: ret +``` + +其中依次调用了`_find_empty_process`和`_copy_process`函数。这两个函数的C代码`find_empty_process()`和`copy_process()`在`kernel/fork.c`中: + +- `find_empty_process()`:寻找一个空闲的pid,并为其在任务数组中为新任务寻找一个空闲项,并返回项号。 +- `copy_process()`:复制父进程的各项值并初始化(这么做是因为复制比新建快)。 + +在这之中,代码为新进程创建了进程控制块`struct task_struct *p`,到`include/linux/sched.h`中跟踪其定义: + +```c +struct task_struct { +/* these are hardcoded - don't touch */ + long state; /* -1 unrunnable, 0 runnable, >0 stopped */ + // 表示进程的状态,-1 表示不可运行,0 表示可运行,大于0 表示已停止。 + long counter; // 计时器,用于调度,当计时器减至0时,进程可能被调度出去。 + long priority; // 进程的优先级。 + long signal; // 当前进程正在处理的信号。 + struct sigaction sigaction[32]; // 存储信号处理程序的数组。 + long blocked; /* bitmap of masked signals */ + // 用于表示被阻塞的信号的位图。 +/* various fields */ + int exit_code; + unsigned long start_code,end_code,end_data,brk,start_stack; + long pid,father,pgrp,session,leader; + // pid 进程ID + // father 父进程ID + // pgrp 进程组ID + // session 会话ID + // leader 会话的领导者ID + unsigned short uid,euid,suid; // 用户ID、有效用户ID、保存的用户ID + unsigned short gid,egid,sgid; // 组ID、有效组ID、保存的组ID + long alarm; // 进程的闹钟定时器 + long utime,stime,cutime,cstime,start_time; + // utime 用户态运行时间 + // stime 系统态运行时间 + // cutime 子进程的用户态运行时间 + // cstime 子进程的系统态运行时间 + // start_time 进程开始运行的时间 + unsigned short used_math; // 表示是否使用了数学协处理器 +/* file system info */ + int tty; // 表示进程是否有终端 + /* -1 if no tty, so it must be signed */ + unsigned short umask; // 文件创建的权限屏蔽掩码 + struct m_inode * pwd; // 当前工作目录 + struct m_inode * root; // 当前根目录 + struct m_inode * executable; // 执行文件的指针 + unsigned long close_on_exec; // 用于在执行新程序时关闭文件的标志位 + struct file * filp[NR_OPEN]; // 文件指针数组,用于表示打开的文件 +/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */ + struct desc_struct ldt[3]; // 进程的局部描述符表,包含代码段、数据段、堆栈段的描述符 +/* tss for this task */ + struct tss_struct tss; // 任务状态段,包含了一些处理器的状态信息 +}; +``` + +最后,返回新建进程的pid。 + +于是,`fork`调用就成功创建了新的进程和pid,并把pid返回给了调用`fork`的进程。 + +## 2.2. exit系统调用和内核函数 + +首先在`init/main.c`-->`main()`中看到了`_exit()`函数: + +```c +if (!(pid=fork())) { + close(0); + if (open("/etc/rc",O_RDONLY,0)) + _exit(1); + execve("/bin/sh",argv_rc,envp_rc); + _exit(2); +} +``` + +追踪`_exit()`,到`lib/_exit.c`中看到定义: + +```c +volatile void _exit(int exit_code) +{ + __asm__("int $0x80"::"a" (__NR_exit),"b" (exit_code)); +} +``` + +和`fork()`一样,`_exit()`也是使用了系统中断来执行。 + +`__NR_exit`的宏定义为系统调用号1,所以实际上:`__NR_exit` 的值被加载到 `%eax` 寄存器;`exit_code` 的值被加载到 `%ebx` 寄存器,作为 `exit` 系统调用的参数,即退出码。 + +于是在`kernel/system_call.s`中,软中断函数`_system_call`代码执行了`call _sys_call_table(,1,4)`,即调用了系统调用表中的`sys_exit`。 + +`sys_exit`的定义在`kernel/exit.c`中: + +```c +int sys_exit(int error_code) +{ + return do_exit((error_code&0xff)<<8); +} +``` + +再追踪`do_exit()`的代码,主要执行以下任务: + +- 释放当前进程的页表 +- 处理与当前进程相关的其他进程 +- 关闭当前进程打开的文件 +- 释放当前进程的pwd、root和executable相关资源 +- 处理当前进程是会话领导者等情况 +- 通知父进程 +- 调度下一个进程 + +至此,本进程结束退出,exit系统调用完毕。 + +## 2.3. 原子性实现 + +纵观代码,进程间的切换主要有两种方式,一种是主动切换,如`pause()`,其作用就是主动将当前进程挂起,切换到下一个进程任务执行,需要调用`schedule()`。在`schedule()`中会判断要操作的进程的状态是否是可中断的。 + +另一种是通过开关中断,主动允许/不允许中断打断当前进程。在`include/asm/system.h`中: + +```c +#define sti() __asm__ ("sti"::) +#define cli() __asm__ ("cli"::) +``` + +因此,在代码中使用`sti()`和`cli()`就能实现开/关中断。 + +在`fork`和`exit`系统调用代码中,并没有调用`pause()`主动切换,也不会因为硬件中断而导致共享资源出错(对栈的使用得当),因此实现了原子性。 + +## 2.4. 修改fork系统调用 + +修改`kernel/fork.c`文件中`copy_process()`函数定义,在`return`前打印出新进程的pid和状态: + +```c +p->state = TASK_RUNNING; /* do this last, just in case */ +printk("pid=%d, state=%d", last_pid, p->state); +return last_pid; +``` + +运行结果如下: + +![p1](p1.png) + +`state`为0表示当前进程(在刚刚创建时)是就绪态和运行态。 + +## 2.5. Makefile文件 + +首先,文件定义了编译必要的工具和选项,以及根设备、目标文件等; + +接下来定义了编译规则,如从`.c`文件编译出`.s`文件等; + +然后定义最后生成`Image`镜像文件; + +规定`Image`文件的构建规则、写入磁盘规则; + +定义构建工具的规则; + +定义编译完后要清理的文件; + +创建备份; + +规定项目源码文件之间构建的依赖关系。 + + + +# 附录 + +编译`kernel/fork.c`: + +```sh +[usr/src/linux/kernel]# gcc -E fork.c -fork.i +[usr/src/linux/kernel]# gcc -S fork.i -fork.s +[usr/src/linux/kernel]# vi fork.s +``` + +则可以看到,函数`verify_area()`变成了`_verify_area`,函数`copy_mem()`变成了`_copy_mem`。 \ No newline at end of file diff --git a/Lab/Lab3/source/p1.png b/Lab/Lab3/source/p1.png new file mode 100644 index 0000000..7b15b9b Binary files /dev/null and b/Lab/Lab3/source/p1.png differ diff --git a/Lab/Lab4/21281280-柯劲帆-第4次实验.pdf b/Lab/Lab4/21281280-柯劲帆-第4次实验.pdf new file mode 100644 index 0000000..0ff688d Binary files /dev/null and b/Lab/Lab4/21281280-柯劲帆-第4次实验.pdf differ diff --git a/Lab/Lab4/code/main.cpp b/Lab/Lab4/code/main.cpp new file mode 100644 index 0000000..9a61793 --- /dev/null +++ b/Lab/Lab4/code/main.cpp @@ -0,0 +1,265 @@ +#include +#include +#include +#include + +int preprocess_task_size(int); +int find_free_block_ff(int, struct Block**); +int find_free_block_bf(int, struct Block**); +int queue_push(int); +int queue_pop(); +int get_command(FILE*); +int fork(int); +void kill(int); +int run(char*); +void init(); +void clean_stdin(); + +#define MAX_NUM_TASKS 128 +#define MAX_NUM_WAIT 512 + +struct Block { + int free; + int start; + int size; + struct Block* next, *last; +}; + +struct Task { + int start; + int size; + int pid; + struct Block* block; +}; + +int sys_size = 384 << 10, usr_size = 128 << 10; +struct Block* sys_space, *usr_space; +struct Task* tasks; +int last_pid = -1; +int match_method = -1; +int wait_queue[MAX_NUM_WAIT]; +int wait_q_begin = 0, wait_q_end = 0; + + +int main() { + init(); + while (get_command(stdin) >= 0); + return 0; +} + +int get_command(FILE* stream) { + printf("user> "); + char input_str[128] = {0}; + fgets(input_str, 127, stream); + if (stream != stdin) printf("%s", input_str); + + int split_index = 0; + for (int i = 0; i < 127; i++) { + if (input_str[i] == '\0') break; + if (input_str[i] == ' ') { + input_str[i] = '\0'; + split_index = i; + } + if (input_str[i] == '\n') { + input_str[i] = '\0'; + break; + } + } + + char* command = input_str, * arguments = input_str + split_index + 1; + + if (!strcmp(command, "exit")) { + return -1; + } + else if (strcmp(command, "fork") == 0) { + int input_size = atoi(arguments); + if (input_size <= 0) { + printf("Bad input size. Input number must larger than ZERO.\n"); + return 0; + } + int pid = fork(input_size); + return 1; + } + else if (!strcmp(command, "kill")) { + int input_pid = atoi(arguments); + if (input_pid < 0) { + printf("Bad input PID. Input number must not smaller than ZERO.\n"); + return 0; + } + kill(input_pid); + return 1; + } + else if (!strcmp(input_str, "print")) { + printf("print.\n"); + return 1; + } + else if (!strcmp(input_str, "run")) { + int result = run(arguments); + return result; + } + else if (!strcmp(input_str, "")); + else { + printf("Command not found.\n"); + return 0; + } +} + +void kill(int pid) { + return; +} + +void init() { + sys_space = (struct Block*)malloc(sizeof(struct Block)); + sys_space->free = 1; + sys_space->start = 0; + sys_space->size = sys_size; + sys_space->next = NULL; + sys_space->last = NULL; + + usr_space = (struct Block*)malloc(sizeof(struct Block)); + usr_space->free = 1; + usr_space->start = sys_size; + usr_space->size = usr_size; + usr_space->next = NULL; + usr_space->last = NULL; + + tasks = (struct Task*)malloc(MAX_NUM_TASKS * sizeof(struct Task)); + for (int i = 0; i < MAX_NUM_TASKS; i++) tasks[i].pid = -1; + +ask_match_method: + printf("Choose a matching method:\n 1. First Fit\n 2. Best Fit\n"); + scanf("%d", &match_method); + clean_stdin(); + if (match_method != 1 && match_method != 2) { + printf("Bad input. Choose again.\n"); + goto ask_match_method; + } +} + +int fork(int size) { + int pid; + int task_index = -1; +repeat: + last_pid++; + if (last_pid < 0) last_pid = 0; + pid = last_pid; + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid >= 0) continue; + if (tasks[i].pid == pid) goto repeat; + task_index = i; + break; + } + if (task_index == -1) return -1; + + size = preprocess_task_size(size); + tasks[task_index].size = size; + int start_loc = -1; + if (match_method == 1) start_loc = find_free_block_ff(size, &tasks[task_index].block); + else start_loc = find_free_block_bf(size, &tasks[task_index].block); + if (start_loc >= 0) { + tasks[task_index].start = start_loc; + tasks[task_index].pid = pid; + tasks[task_index].size = size; + printf("Fork succeed. PID is %d . Block size is %d bit.\n", pid, size); + return pid; + } + printf("Fork failed. Out of Memory. Trying to push into wait list.\n"); + int tmp = queue_push(size); + if (tmp < 0) printf("Error: Push into wait list failed.\n"); + else printf("Push into wait list succeed.\n"); + return -1; +} + +int find_free_block_ff(int size, struct Block** block_p) { + struct Block* now = usr_space; + while (1) { + if (now == NULL) return -1; + if (now->free != 0 && now->size >= size) break; + now = now->next; + } + *block_p = now; + if (now->size == size) return now->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->free = 1; + free_space->last = now; + free_space->next = now->next; + free_space->size = now->size - size; + free_space->start = now->start + size; + + now->free = 0; + now->next = free_space; + now->size = size; + return now->start; +} + +int find_free_block_bf(int size, struct Block** block_p) { + struct Block* now = usr_space, *best_block; + int best_size = 0xffffff; + while (1) { + if (now == NULL) break; + if (now->free != 0 && now->size >= size && best_size >= now->size) { + best_block = now; + best_size = now->size; + } + now = now->next; + } + if (best_size == 0xffffff) return -1; + *block_p = best_block; + if (best_block->size == size) return best_block->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->free = 1; + free_space->last = best_block; + free_space->next = best_block->next; + free_space->size = best_block->size - size; + free_space->start = best_block->start + size; + + best_block->free = 0; + best_block->next = free_space; + best_block->size = size; + return best_block->start; +} + +int run(char* path) { + FILE* f = NULL; + f = fopen(path, "r+"); + if (f == NULL) { + printf("Error: Invalid file path.\n"); + return 0; + } + int result = 0; + while(!feof(f) && result != -1) result = get_command(f); + fclose(f); + printf("%d\n", result); + return result; +} + +int preprocess_task_size(int size) { + size = size & 0x400; + size = size + 0x400; + return size; +} + +int queue_push(int size) { + if ((wait_q_end + 1) % MAX_NUM_WAIT == wait_q_begin) { + printf("Wait list full.\n"); + return -1; + } + wait_queue[wait_q_end++] = size; + wait_q_end %= MAX_NUM_WAIT; + return 1; +} + +int queue_pop() { + if (wait_q_begin == wait_q_end) { + printf("Wait list empty.\n"); + return -1; + } + return wait_queue[wait_q_begin++]; +} + +void clean_stdin() { + int c; + do { + c = getchar(); + } while (c != '\n' && c != EOF); +} \ No newline at end of file diff --git a/Lab/Lab4/code/main.exe b/Lab/Lab4/code/main.exe new file mode 100644 index 0000000..3100160 Binary files /dev/null and b/Lab/Lab4/code/main.exe differ diff --git a/Lab/Lab4/code/test.txt b/Lab/Lab4/code/test.txt new file mode 100644 index 0000000..165922a --- /dev/null +++ b/Lab/Lab4/code/test.txt @@ -0,0 +1,4 @@ +fork 1023 +fork -1 +exit +111 diff --git a/Lab/Lab4/requirements/ZGSOS操作系统实验指导《实验课题12_动态可重定位分区内存管理模拟设计与实现》.pdf b/Lab/Lab4/requirements/ZGSOS操作系统实验指导《实验课题12_动态可重定位分区内存管理模拟设计与实现》.pdf new file mode 100644 index 0000000..99ecd4f Binary files /dev/null and b/Lab/Lab4/requirements/ZGSOS操作系统实验指导《实验课题12_动态可重定位分区内存管理模拟设计与实现》.pdf differ diff --git a/Lab/Lab4/source/21281280-柯劲帆-第4次实验.md b/Lab/Lab4/source/21281280-柯劲帆-第4次实验.md new file mode 100644 index 0000000..0c0ae55 --- /dev/null +++ b/Lab/Lab4/source/21281280-柯劲帆-第4次实验.md @@ -0,0 +1,824 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:动态可重定位分区内存管理模拟设计与实现
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年11月26日
+
+ + + +--- + +## 目录 + +[TOC] + +--- + + + +# 1. 开发运行环境和工具 + +| 操作系统 | Linux内核版本 | 处理器 | GCC版本 | +| :---------: | :-----------------------: | :--------------------------------------: | :-------------------------------: | +| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | Intel(R) Core(TM) i3-2330M CPU @ 2.20GHz | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 | + + - **其他工具**: + - **编辑**:VSCode + - **编译和运行**:Terminal + + + +# 2. 实验过程、分析和结论 + +## 2.1. 初始化 + +```c +#include +#include +#include +#include + +#define MAX_NUM_TASKS 128 + +struct Block { + int start; + int size; + int pid; + struct Block* next, * last; +}; + +struct Task { + int start; + int size; + int pid; + struct Block* block; +}; + +struct Request { + int size; + struct Request* next; +}; + + +int preprocess_task_size(int); +int find_free_block_ff(int, struct Block**); +int find_free_block_bf(int, struct Block**); +void queue_push(int); +int queue_pop(); +int get_command(FILE*); +int fork_process(int); +void kill_process(int); +int run(char*); +void init(); +void clean_stdin(); +void execute_wait_list(); +void print(); + +int sys_size = 128 << 20, usr_size = 384 << 20; +struct Block* sys_space = NULL, * usr_space = NULL; +struct Task* tasks = NULL; +struct Request* waitlist_begin = NULL, * waitlist_end = NULL; +int waitlist_size = 0; +int last_pid = -1; +int match_method = -1; + + +int main() { + init(); + while (get_command(stdin) >= 0); + return 0; +} + +void init() { + sys_space = (struct Block*)malloc(sizeof(struct Block)); + sys_space->pid = -1; + sys_space->start = 0; + sys_space->size = sys_size; + sys_space->next = NULL; + sys_space->last = NULL; + + usr_space = (struct Block*)malloc(sizeof(struct Block)); + usr_space->pid = -1; + usr_space->start = sys_size; + usr_space->size = usr_size; + usr_space->next = NULL; + usr_space->last = NULL; + + tasks = (struct Task*)malloc(MAX_NUM_TASKS * sizeof(struct Task)); + for (int i = 0; i < MAX_NUM_TASKS; i++) tasks[i].pid = -1; + +ask_match_method: + printf("Choose a matching method:\n 1. First Fit\n 2. Best Fit\n"); + printf("choose> "); + scanf("%d", &match_method); + clean_stdin(); + if (match_method != 1 && match_method != 2) { + printf("Bad input. Choose again.\n"); + goto ask_match_method; + } + + waitlist_begin = (struct Request*)malloc(sizeof(struct Request)); + waitlist_end = waitlist_begin; + waitlist_begin->next = NULL; + waitlist_begin->size = -1; +} +``` + + + +## 2.2. 申请内存分配操作 + +```c +int fork_process(int size) { + int pid; + int task_index = -1; +repeat: + last_pid++; + if (last_pid < 0) last_pid = 0; + pid = last_pid; + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid >= 0 && tasks[i].pid == pid) goto repeat; + } + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid >= 0) continue; + task_index = i; + break; + } + if (task_index == -1) return -1; + + size = preprocess_task_size(size); + tasks[task_index].size = size; + int start_loc = -1; + if (match_method == 1) start_loc = find_free_block_ff(size, &tasks[task_index].block); + else start_loc = find_free_block_bf(size, &tasks[task_index].block); + if (start_loc >= 0) { + tasks[task_index].start = start_loc; + tasks[task_index].pid = pid; + tasks[task_index].size = size; + tasks[task_index].block->pid = pid; + printf("Fork succeed. PID is %d . Block size is %d bits.\n", pid, size); + return pid; + } + printf("Fork failed. Out of Memory. Trying to push into wait list.\n"); + queue_push(size); + return -1; +} + +int find_free_block_ff(int size, struct Block** block_p) { + struct Block* now = usr_space; + int cnt = 1; + while (1) { + if (now == NULL) return -1; + if (now->pid < 0 && now->size >= size) break; + now = now->next; + cnt++; + } + printf("Use %d times to find free block.\n", cnt); + *block_p = now; + if (now->size == size) return now->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->pid = -1; + free_space->last = now; + free_space->next = now->next; + free_space->size = now->size - size; + free_space->start = now->start + size; + + now->pid = -2; + now->next = free_space; + now->size = size; + return now->start; +} + +int find_free_block_bf(int size, struct Block** block_p) { + struct Block* now = usr_space, * best_block; + int best_size = 0x7fffffff; + int cnt = 1; + while (1) { + if (now == NULL) break; + if (now->pid < 0 && now->size >= size && best_size >= now->size) { + best_block = now; + best_size = now->size; + } + now = now->next; + cnt++; + } + if (best_size == 0x7fffffff) return -1; + printf("Use %d times to find free block.\n", cnt); + *block_p = best_block; + if (best_block->size == size) return best_block->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->pid = -1; + free_space->last = best_block; + free_space->next = best_block->next; + free_space->size = best_block->size - size; + free_space->start = best_block->start + size; + + best_block->pid = -2; + best_block->next = free_space; + best_block->size = size; + return best_block->start; +} + +int run(char* path) { + FILE* f = NULL; + f = fopen(path, "r+"); + if (f == NULL) { + printf("Error: Invalid file path.\n"); + return 0; + } + int result = 0; + while (!feof(f) && result != -1) result = get_command(f); + fclose(f); + return result; +} + +int preprocess_task_size(int size) { + int lower = size & 0x3ff; + if (lower == 0) return size; + int upper = size - lower; + size = upper + 0x400; + return size; +} + +void queue_push(int size) { + waitlist_begin->next = (struct Request*)malloc(sizeof(struct Request)); + waitlist_begin = waitlist_begin->next; + waitlist_begin->next = NULL; + waitlist_begin->size = size; + waitlist_size++; +} + +int queue_pop() { + if (!waitlist_size) { + printf("Wait list empty.\n"); + return -1; + } + struct Request* tmp = waitlist_end; + waitlist_end = waitlist_end->next; + free(tmp); + waitlist_size--; + return waitlist_end->size; +} +``` + + + +## 2.3. 回收 + +```c +void kill_process(int pid) { + struct Block* block = NULL; + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid != pid) continue; + + block = tasks[i].block; + break; + } + if (block == NULL) { + printf("Error: PID dosen't exist.\n"); + return; + } + if (block->last && block->last->pid < 0 && block->next && block->next->pid < 0) { + struct Block* free_block = block->last; + free_block->next = block->next->next; + free_block->size += block->size + block->next->size; + if (free_block->next) free_block->next->last = free_block; + free(block->next); + free(block); + } + else if (block->last && block->last->pid < 0) { + struct Block* free_block = block->last; + free_block->next = block->next; + free_block->size += block->size; + if (free_block->next) free_block->next->last = free_block; + free(block); + } + else if (block->next && block->next->pid < 0) { + struct Block* free_block = block; + free_block->size += block->next->size; + free_block->next = block->next->next; + free_block->pid = -1; + if (free_block->next) free_block->next->last = free_block; + free(block->next); + } + else { + block->pid = -1; + } + + execute_wait_list(); + + return; +} + +void execute_wait_list() { + int size = -1, loop_num = waitlist_size; + for (int i = 0; i < loop_num; i++) { + size = queue_pop(); + fork_process(size); + } +} +``` + + + +## 2.4. 其他代码 + +```c +int get_command(FILE* stream) { + printf("user> "); + char input_str[128] = { 0 }; + fgets(input_str, 127, stream); + if (stream != stdin) printf("%s", input_str); + + int split_index = 0; + for (int i = 0; i < 127; i++) { + if (input_str[i] == '\0') break; + if (input_str[i] == ' ') { + input_str[i] = '\0'; + split_index = i; + } + if (input_str[i] == '\n') { + input_str[i] = '\0'; + break; + } + } + + char* command = input_str, * arguments = input_str + split_index + 1; + + if (!strcmp(command, "exit")) { + return -1; + } + else if (strcmp(command, "fork") == 0) { + int input_size = atoi(arguments); + if (input_size <= 0) { + printf("Bad input size. Input number must larger than ZERO.\n"); + return 0; + } + else if (input_size > usr_size) { + printf("Bad input size. Input number must smaller than %d bits.\n", usr_size); + return 0; + } + int pid = fork_process(input_size); + return 1; + } + else if (!strcmp(command, "kill")) { + int input_pid = atoi(arguments); + if (input_pid < 0) { + printf("Bad input PID. Input number must not smaller than ZERO.\n"); + return 0; + } + kill_process(input_pid); + return 1; + } + else if (!strcmp(input_str, "print")) { + print(); + return 1; + } + else if (!strcmp(input_str, "run")) { + int result = run(arguments); + return result; + } + else if (input_str[0] == '\0'); + else { + printf("Command not found.\n"); + return 0; + } +} + +void print() { + char str[2][13] = { {" True"}, {" False"} }; + char* s; + struct Block* p = sys_space; + printf("+----------------------------------------------------------------+\n"); + printf("| System Space |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + printf("| Free | Begin | Size | End | PID |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + while (p != NULL) { + s = p->pid < 0 ? str[0] : str[1]; + printf("|%s|%12d|%12d|%12d|%12d|\n", s, p->start, p->size, p->start + p->size - 1, p->pid); + printf("+------------+------------+------------+------------+------------+\n"); + p = p->next; + } + printf("+----------------------------------------------------------------+\n\n"); + + p = usr_space; + printf("+----------------------------------------------------------------+\n"); + printf("| User Space |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + printf("| Free | Begin | Size | End | PID |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + while (p != NULL) { + s = p->pid < 0 ? str[0] : str[1]; + printf("|%s|%12d|%12d|%12d|%12d|\n", s, p->start, p->size, p->start + p->size - 1, p->pid); + printf("+------------+------------+------------+------------+------------+\n"); + p = p->next; + } + printf("+----------------------------------------------------------------+\n"); +} + +void clean_stdin() { + int c; + do { + c = getchar(); + } while (c != '\n' && c != EOF); +} + +int run(char* path) { + FILE* f = NULL; + f = fopen(path, "r+"); + if (f == NULL) { + printf("Error: Invalid file path.\n"); + return 0; + } + int result = 0; + while (!feof(f) && result != -1) result = get_command(f); + fclose(f); + return result; +} +``` + + + +# 附录 + +```c +#include +#include +#include +#include + +#define MAX_NUM_TASKS 128 + +struct Block { + int start; + int size; + int pid; + struct Block* next, * last; +}; + +struct Task { + int start; + int size; + int pid; + struct Block* block; +}; + +struct Request { + int size; + struct Request* next; +}; + + +int preprocess_task_size(int); +int find_free_block_ff(int, struct Block**); +int find_free_block_bf(int, struct Block**); +void queue_push(int); +int queue_pop(); +int get_command(FILE*); +int fork_process(int); +void kill_process(int); +int run(char*); +void init(); +void clean_stdin(); +void execute_wait_list(); +void print(); + +int sys_size = 128 << 20, usr_size = 384 << 20; +struct Block* sys_space = NULL, * usr_space = NULL; +struct Task* tasks = NULL; +struct Request* waitlist_begin = NULL, * waitlist_end = NULL; +int waitlist_size = 0; +int last_pid = -1; +int match_method = -1; + + +int main() { + init(); + while (get_command(stdin) >= 0); + return 0; +} + +int get_command(FILE* stream) { + printf("user> "); + char input_str[128] = { 0 }; + fgets(input_str, 127, stream); + if (stream != stdin) printf("%s", input_str); + + int split_index = 0; + for (int i = 0; i < 127; i++) { + if (input_str[i] == '\0') break; + if (input_str[i] == ' ') { + input_str[i] = '\0'; + split_index = i; + } + if (input_str[i] == '\n') { + input_str[i] = '\0'; + break; + } + } + + char* command = input_str, * arguments = input_str + split_index + 1; + + if (!strcmp(command, "exit")) { + return -1; + } + else if (strcmp(command, "fork") == 0) { + int input_size = atoi(arguments); + if (input_size <= 0) { + printf("Bad input size. Input number must larger than ZERO.\n"); + return 0; + } + else if (input_size > usr_size) { + printf("Bad input size. Input number must smaller than %d bits.\n", usr_size); + return 0; + } + int pid = fork_process(input_size); + return 1; + } + else if (!strcmp(command, "kill")) { + int input_pid = atoi(arguments); + if (input_pid < 0) { + printf("Bad input PID. Input number must not smaller than ZERO.\n"); + return 0; + } + kill_process(input_pid); + return 1; + } + else if (!strcmp(input_str, "print")) { + print(); + return 1; + } + else if (!strcmp(input_str, "run")) { + int result = run(arguments); + return result; + } + else if (input_str[0] == '\0'); + else { + printf("Command not found.\n"); + return 0; + } +} + +void kill_process(int pid) { + struct Block* block = NULL; + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid != pid) continue; + + block = tasks[i].block; + break; + } + if (block == NULL) { + printf("Error: PID dosen't exist.\n"); + return; + } + if (block->last && block->last->pid < 0 && block->next && block->next->pid < 0) { + struct Block* free_block = block->last; + free_block->next = block->next->next; + free_block->size += block->size + block->next->size; + if (free_block->next) free_block->next->last = free_block; + free(block->next); + free(block); + } + else if (block->last && block->last->pid < 0) { + struct Block* free_block = block->last; + free_block->next = block->next; + free_block->size += block->size; + if (free_block->next) free_block->next->last = free_block; + free(block); + } + else if (block->next && block->next->pid < 0) { + struct Block* free_block = block; + free_block->size += block->next->size; + free_block->next = block->next->next; + free_block->pid = -1; + if (free_block->next) free_block->next->last = free_block; + free(block->next); + } + else { + block->pid = -1; + } + + execute_wait_list(); + + return; +} + +void init() { + sys_space = (struct Block*)malloc(sizeof(struct Block)); + sys_space->pid = -1; + sys_space->start = 0; + sys_space->size = sys_size; + sys_space->next = NULL; + sys_space->last = NULL; + + usr_space = (struct Block*)malloc(sizeof(struct Block)); + usr_space->pid = -1; + usr_space->start = sys_size; + usr_space->size = usr_size; + usr_space->next = NULL; + usr_space->last = NULL; + + tasks = (struct Task*)malloc(MAX_NUM_TASKS * sizeof(struct Task)); + for (int i = 0; i < MAX_NUM_TASKS; i++) tasks[i].pid = -1; + +ask_match_method: + printf("Choose a matching method:\n 1. First Fit\n 2. Best Fit\n"); + printf("choose> "); + scanf("%d", &match_method); + clean_stdin(); + if (match_method != 1 && match_method != 2) { + printf("Bad input. Choose again.\n"); + goto ask_match_method; + } + + waitlist_begin = (struct Request*)malloc(sizeof(struct Request)); + waitlist_end = waitlist_begin; + waitlist_begin->next = NULL; + waitlist_begin->size = -1; +} + +int fork_process(int size) { + int pid; + int task_index = -1; +repeat: + last_pid++; + if (last_pid < 0) last_pid = 0; + pid = last_pid; + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid >= 0 && tasks[i].pid == pid) goto repeat; + } + for (int i = 0; i < MAX_NUM_TASKS; i++) { + if (tasks[i].pid >= 0) continue; + task_index = i; + break; + } + if (task_index == -1) return -1; + + size = preprocess_task_size(size); + tasks[task_index].size = size; + int start_loc = -1; + if (match_method == 1) start_loc = find_free_block_ff(size, &tasks[task_index].block); + else start_loc = find_free_block_bf(size, &tasks[task_index].block); + if (start_loc >= 0) { + tasks[task_index].start = start_loc; + tasks[task_index].pid = pid; + tasks[task_index].size = size; + tasks[task_index].block->pid = pid; + printf("Fork succeed. PID is %d . Block size is %d bits.\n", pid, size); + return pid; + } + printf("Fork failed. Out of Memory. Trying to push into wait list.\n"); + queue_push(size); + return -1; +} + +int find_free_block_ff(int size, struct Block** block_p) { + struct Block* now = usr_space; + int cnt = 1; + while (1) { + if (now == NULL) return -1; + if (now->pid < 0 && now->size >= size) break; + now = now->next; + cnt++; + } + printf("Use %d times to find free block.\n", cnt); + *block_p = now; + if (now->size == size) return now->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->pid = -1; + free_space->last = now; + free_space->next = now->next; + free_space->size = now->size - size; + free_space->start = now->start + size; + + now->pid = -2; + now->next = free_space; + now->size = size; + return now->start; +} + +int find_free_block_bf(int size, struct Block** block_p) { + struct Block* now = usr_space, * best_block; + int best_size = 0x7fffffff; + int cnt = 1; + while (1) { + if (now == NULL) break; + if (now->pid < 0 && now->size >= size && best_size >= now->size) { + best_block = now; + best_size = now->size; + } + now = now->next; + cnt++; + } + if (best_size == 0x7fffffff) return -1; + printf("Use %d times to find free block.\n", cnt); + *block_p = best_block; + if (best_block->size == size) return best_block->start; + struct Block* free_space = (struct Block*)malloc(sizeof(struct Block)); + free_space->pid = -1; + free_space->last = best_block; + free_space->next = best_block->next; + free_space->size = best_block->size - size; + free_space->start = best_block->start + size; + + best_block->pid = -2; + best_block->next = free_space; + best_block->size = size; + return best_block->start; +} + +int run(char* path) { + FILE* f = NULL; + f = fopen(path, "r+"); + if (f == NULL) { + printf("Error: Invalid file path.\n"); + return 0; + } + int result = 0; + while (!feof(f) && result != -1) result = get_command(f); + fclose(f); + return result; +} + +int preprocess_task_size(int size) { + int lower = size & 0x3ff; + if (lower == 0) return size; + int upper = size - lower; + size = upper + 0x400; + return size; +} + +void queue_push(int size) { + waitlist_begin->next = (struct Request*)malloc(sizeof(struct Request)); + waitlist_begin = waitlist_begin->next; + waitlist_begin->next = NULL; + waitlist_begin->size = size; + waitlist_size++; +} + +int queue_pop() { + if (!waitlist_size) { + printf("Wait list empty.\n"); + return -1; + } + struct Request* tmp = waitlist_end; + waitlist_end = waitlist_end->next; + free(tmp); + waitlist_size--; + return waitlist_end->size; +} + +void clean_stdin() { + int c; + do { + c = getchar(); + } while (c != '\n' && c != EOF); +} + +void execute_wait_list() { + int size = -1, loop_num = waitlist_size; + for (int i = 0; i < loop_num; i++) { + size = queue_pop(); + fork_process(size); + } +} + +void print() { + char str[2][13] = { {" True"}, {" False"} }; + char* s; + struct Block* p = sys_space; + printf("+----------------------------------------------------------------+\n"); + printf("| System Space |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + printf("| Free | Begin | Size | End | PID |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + while (p != NULL) { + s = p->pid < 0 ? str[0] : str[1]; + printf("|%s|%12d|%12d|%12d|%12d|\n", s, p->start, p->size, p->start + p->size - 1, p->pid); + printf("+------------+------------+------------+------------+------------+\n"); + p = p->next; + } + printf("+----------------------------------------------------------------+\n\n"); + + p = usr_space; + printf("+----------------------------------------------------------------+\n"); + printf("| User Space |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + printf("| Free | Begin | Size | End | PID |\n"); + printf("+------------+------------+------------+------------+------------+\n"); + while (p != NULL) { + s = p->pid < 0 ? str[0] : str[1]; + printf("|%s|%12d|%12d|%12d|%12d|\n", s, p->start, p->size, p->start + p->size - 1, p->pid); + printf("+------------+------------+------------+------------+------------+\n"); + p = p->next; + } + printf("+----------------------------------------------------------------+\n"); +} +``` diff --git a/Lab/Lab4/source/21281280-柯劲帆-第4次实验.pdf b/Lab/Lab4/source/21281280-柯劲帆-第4次实验.pdf new file mode 100644 index 0000000..0ff688d Binary files /dev/null and b/Lab/Lab4/source/21281280-柯劲帆-第4次实验.pdf differ diff --git a/Lab/Lab5/21281280-柯劲帆-第5次实验.pdf b/Lab/Lab5/21281280-柯劲帆-第5次实验.pdf new file mode 100644 index 0000000..07f0700 Binary files /dev/null and b/Lab/Lab5/21281280-柯劲帆-第5次实验.pdf differ diff --git a/Lab/Lab5/code/main.c b/Lab/Lab5/code/main.c new file mode 100644 index 0000000..ce64e77 --- /dev/null +++ b/Lab/Lab5/code/main.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include + +#define REQUEST_LENGTH 100 +#define DISK_TRACKS 200 + +double fcfs(int* requestArray, int requestLength, int headStart, int* outputArray); +double sstf(int* requestArray, int requestLength, int headStart, int* outputArray); +double scan(int* requestArray, int requestLength, int headStart, int* outputArray); +double cscan(int* requestArray, int requestLength, int headStart, int* outputArray); +double fscan(int* requestArray1, int requestLength1, int* requestArray2, int requestLength2, int headStart, int *outputArray); + + +// 生成随机磁道访问序列 +void generateRandomSequence(int* sequence, int length) { + for (int i = 0; i < length; i++) { + sequence[i] = rand() % DISK_TRACKS; + } +} + +// 打印磁道访问顺序及平均寻道数 +void printResults(const char* algorithmName, int* accessSequence, int length, double avgSeek) { + printf("%s Algorithm:\n", algorithmName); + printf("Visit Order: "); + for (int i = 0; i < length; i++) { + printf("%d ", accessSequence[i]); + } + printf("\nAvg Seek Times: %.2f\n\n", avgSeek); +} + +int main() { + int requestSequence[REQUEST_LENGTH]; + int *outputSequence[REQUEST_LENGTH]; + int tempSequence[REQUEST_LENGTH]; + double avgSeek; + int headStart = 50; // 假设的起始磁头位置 + + // 生成随机磁道访问序列 + srand((unsigned)time(NULL)); + generateRandomSequence(requestSequence, REQUEST_LENGTH); + + // 测试 FCFS 算法 + memcpy(tempSequence, requestSequence, sizeof(requestSequence)); + avgSeek = fcfs(tempSequence, REQUEST_LENGTH, headStart, outputSequence); + printResults("FCFS", outputSequence, REQUEST_LENGTH, avgSeek); + + // 测试 SSTF 算法 + memcpy(tempSequence, requestSequence, sizeof(requestSequence)); + avgSeek = sstf(tempSequence, REQUEST_LENGTH, headStart, outputSequence); + printResults("SSTF", outputSequence, REQUEST_LENGTH, avgSeek); + + // 测试 SCAN 算法 + memcpy(tempSequence, requestSequence, sizeof(requestSequence)); + avgSeek = scan(tempSequence, REQUEST_LENGTH, headStart, outputSequence); + printResults("SCAN", outputSequence, REQUEST_LENGTH, avgSeek); + + // 测试 CSCAN 算法 + memcpy(tempSequence, requestSequence, sizeof(requestSequence)); + avgSeek = cscan(tempSequence, REQUEST_LENGTH, headStart, outputSequence); + printResults("CSCAN", outputSequence, REQUEST_LENGTH, avgSeek); + + // 测试 FSCAN 算法 + // 注意:FSCAN需要两个队列,但为简化,在这里使用相同的队列两次 + memcpy(tempSequence, requestSequence, sizeof(requestSequence)); + avgSeek = fscan(requestSequence, REQUEST_LENGTH / 2, requestSequence + REQUEST_LENGTH / 2, REQUEST_LENGTH / 2, headStart, outputSequence); + printResults("FSCAN", outputSequence, REQUEST_LENGTH, avgSeek); + + return 0; +} + + +double fcfs(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalMovement = 0; + int currentPosition = headStart; + + for (int i = 0; i < requestLength; i++) { + // 计算当前请求和磁头位置之间的距离 + totalMovement += abs(requestArray[i] - currentPosition); + // 移动磁头到当前请求位置 + currentPosition = requestArray[i]; + // 记录移动过程 + outputArray[i] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalMovement / requestLength; +} + +double sstf(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + int minDistance, closestIndex; + + // 初始化一个数组来标记已处理的请求 + int processed[REQUEST_LENGTH] = {0}; + for (int i = 0; i < requestLength; i++) processed[i] = 0; + + while (processedCount < requestLength) { + minDistance = 2147483647; + + // 找到最近的请求 + for (int i = 0; i < requestLength; i++) { + if (!processed[i] && abs(requestArray[i] - currentPosition) < minDistance) { + minDistance = abs(requestArray[i] - currentPosition); + closestIndex = i; + } + } + + // 处理这个请求 + totalSeekCount += minDistance; + currentPosition = requestArray[closestIndex]; + processed[closestIndex] = 1; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} + +int compare(const void *a, const void *b) { + return (*(int*)a - *(int*)b); +} + +double scan(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + + // 使用qsort进行排序 + qsort(requestArray, requestLength, sizeof(int), compare); + + // 找到起始位置最近的请求 + int startIndex; + for (startIndex = 0; startIndex < requestLength; startIndex++) { + if (requestArray[startIndex] >= headStart) break; + } + + // 先向上移动 + for (int i = startIndex; i < requestLength; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 然后向下移动 + for (int i = startIndex - 1; i >= 0; i--) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} + +double cscan(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + + // 使用 qsort 进行排序 + qsort(requestArray, requestLength, sizeof(int), compare); + + // 找到起始位置最近的请求索引 + int startIndex; + for (startIndex = 0; startIndex < requestLength; startIndex++) { + if (requestArray[startIndex] >= headStart) break; + } + + // 先处理磁头上方的请求 + for (int i = startIndex; i < requestLength; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 如果有必要,跳转到最低请求 + if (startIndex != 0) { + totalSeekCount += abs(currentPosition - requestArray[0]); + currentPosition = requestArray[0]; + } + + // 然后处理磁头下方的请求 + for (int i = 0; i < startIndex; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} + +void processQueue(int* queue, int length, int* currentPosition, int* totalSeekCount, int* outputArray, int* processedCount) { + // 对队列进行排序 + qsort(queue, length, sizeof(int), compare); + + // 找到最接近当前磁头位置的请求 + int i = 0; + while (i < length && queue[i] < *currentPosition) { + i++; + } + + // 先处理磁头位置之后(更高磁道号)的请求 + for (int j = i; j < length; j++) { + *totalSeekCount += abs(*currentPosition - queue[j]); + *currentPosition = queue[j]; + outputArray[(*processedCount)++] = queue[j]; + } + + // 再处理磁头位置之前(更低磁道号)的请求 + for (int j = i - 1; j >= 0; j--) { + *totalSeekCount += abs(*currentPosition - queue[j]); + *currentPosition = queue[j]; + outputArray[(*processedCount)++] = queue[j]; + } +} + +double fscan(int* requestArray1, int requestLength1, int* requestArray2, int requestLength2, int headStart, int* outputArray) { + int totalSeekCount = 0; + int currentPosition = headStart; + int processedCount = 0; + + // 处理第一个队列 + processQueue(requestArray1, requestLength1, ¤tPosition, &totalSeekCount, outputArray, &processedCount); + + // 处理第二个队列 + processQueue(requestArray2, requestLength2, ¤tPosition, &totalSeekCount, outputArray + requestLength1, &processedCount); + + // 计算平均寻道数 + return (double)totalSeekCount / (requestLength1 + requestLength2); +} + + diff --git a/Lab/Lab5/source/21281280-柯劲帆-第5次实验.md b/Lab/Lab5/source/21281280-柯劲帆-第5次实验.md new file mode 100644 index 0000000..2bc35ec --- /dev/null +++ b/Lab/Lab5/source/21281280-柯劲帆-第5次实验.md @@ -0,0 +1,380 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:移动头磁盘调度算法模拟实现与比较
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年12月10日
+
+ + + + +--- + +## 目录 + +[TOC] + +--- + + + +# 1. 开发运行环境和工具 + +| 操作系统 | Linux内核版本 | 处理器 | GCC版本 | +| :---------: | :-----------------------: | :--------------------------------------: | :-------------------------------: | +| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | Intel(R) Core(TM) i3-2330M CPU @ 2.20GHz | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 | + + - **其他工具**: + - **编辑**:VSCode + - **编译和运行**:Terminal + + + +# 2. 实验过程、分析和结论 + +## 2.1. FCFS + +```c +double fcfs(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalMovement = 0; + int currentPosition = headStart; + + for (int i = 0; i < requestLength; i++) { + // 计算当前请求和磁头位置之间的距离 + totalMovement += abs(requestArray[i] - currentPosition); + // 移动磁头到当前请求位置 + currentPosition = requestArray[i]; + // 记录移动过程 + outputArray[i] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalMovement / requestLength; +} +``` + +该部分测试输出为: + +```txt +FCFS Algorithm: +Visit Order: 176 32 118 25 69 54 180 129 112 63 176 107 109 74 186 89 31 31 189 177 90 25 179 103 64 110 24 192 121 12 75 144 49 70 138 151 29 168 166 48 54 175 191 50 193 101 121 26 54 11 119 144 96 167 142 1 12 41 102 93 45 153 132 146 151 67 27 121 83 197 173 71 195 64 189 47 31 197 182 157 158 114 33 102 193 197 155 175 188 89 21 27 94 151 73 33 4 110 137 173 +Avg Seek Times: 66.65 +``` + +FCFS算法非常简单,易于实现。然而,它并不是最优化的磁盘调度算法,因为它没有考虑磁头移动的最短路径,可能导致较长的平均寻道时间。这可能导致磁盘性能不佳,特别是在请求频繁变化的环境中。 + + + +## 2.2. SSTF + +```c +double sstf(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + int minDistance, closestIndex; + + // 初始化一个数组来标记已处理的请求 + int processed[REQUEST_LENGTH] = {0}; + for (int i = 0; i < requestLength; i++) processed[i] = 0; + + while (processedCount < requestLength) { + minDistance = 2147483647; + + // 找到最近的请求 + for (int i = 0; i < requestLength; i++) { + if (!processed[i] && abs(requestArray[i] - currentPosition) < minDistance) { + minDistance = abs(requestArray[i] - currentPosition); + closestIndex = i; + } + } + + // 处理这个请求 + totalSeekCount += minDistance; + currentPosition = requestArray[closestIndex]; + processed[closestIndex] = 1; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} + +int compare(const void *a, const void *b) { + return (*(int*)a - *(int*)b); +} +``` + +该部分测试输出为: + +```txt +SSTF Algorithm: +Visit Order: 50 49 48 47 45 41 33 33 32 31 31 31 29 27 27 26 25 25 24 21 12 12 11 4 1 54 54 54 63 64 64 67 69 70 71 73 74 75 83 89 89 90 93 94 96 101 102 102 103 107 109 110 110 112 114 118 119 121 121 121 129 132 137 138 142 144 144 146 151 151 151 153 155 157 158 166 167 168 173 173 175 175 176 176 177 179 180 182 186 188 189 189 191 192 193 193 195 197 197 197 +Avg Seek Times: 2.45 +``` + +相比于FCFS,SSTF算法在平均寻道时间上有显著提升。这是因为它通过选择最近的请求来减少每次磁头移动的距离,从而降低了总寻道距离。算法中使用 `abs(requestArray[i] - currentPosition)` 计算与当前磁头位置的距离,并选择最近的请求。这种方法更有效地利用了磁头的当前位置。 + +尽管SSTF在性能上优于FCFS,但它可能导致远离当前磁头位置的请求长时间等待,即“饥饿”问题。这在请求分布不均匀时尤其明显。 + + + +## 2.3. SCAN + +```c +int compare(const void *a, const void *b) { + return (*(int*)a - *(int*)b); +} + +double scan(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + + outputArray = (int*)malloc(sizeof(int) * requestLength); + + // 使用qsort进行排序 + qsort(requestArray, requestLength, sizeof(int), compare); + + // 找到起始位置最近的请求 + int startIndex; + for (startIndex = 0; startIndex < requestLength; startIndex++) { + if (requestArray[startIndex] >= headStart) break; + } + + // 先向上移动 + for (int i = startIndex; i < requestLength; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 然后向下移动 + for (int i = startIndex - 1; i >= 0; i--) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} +``` + +该部分测试输出为: + +```txt +SCAN Algorithm: +Visit Order: 50 54 54 54 63 64 64 67 69 70 71 73 74 75 83 89 89 90 93 94 96 101 102 102 103 107 109 110 110 112 114 118 119 121 121 121 129 132 137 138 142 144 144 146 151 151 151 153 155 157 158 166 167 168 173 173 175 175 176 176 177 179 180 182 186 188 189 189 191 192 193 193 195 197 197 197 49 48 47 45 41 33 33 32 31 31 31 29 27 27 26 25 25 24 21 12 12 11 4 1 +Avg Seek Times: 3.43 +``` + +SCAN算法通过优化磁头移动的方向来减少寻道距离。它首先向一个方向移动,直到到达最远的请求,然后转向。 相比于SSTF,SCAN算法可以更好地避免饥饿问题。由于磁头最终会访问每个位置,因此所有的请求最终都会被服务。 + +SCAN算法提供了一种均衡的磁盘调度策略,既减少了平均寻道时间,又避免了长时间等待请求的饥饿问题。实验结果表明,虽然其平均寻道时间略高于SSTF,但在处理大量分散请求时,SCAN算法能提供更加稳定和公平的服务。 + + + +## 2.4. CSCAN + +```c +double cscan(int* requestArray, int requestLength, int headStart, int* outputArray) { + int totalSeekCount = 0; + int processedCount = 0; + int currentPosition = headStart; + + outputArray = (int*)malloc(sizeof(int) * requestLength); + + // 使用 qsort 进行排序 + qsort(requestArray, requestLength, sizeof(int), compare); + + // 找到起始位置最近的请求索引 + int startIndex; + for (startIndex = 0; startIndex < requestLength; startIndex++) { + if (requestArray[startIndex] >= headStart) break; + } + + // 先处理磁头上方的请求 + for (int i = startIndex; i < requestLength; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 如果有必要,跳转到最低请求 + if (startIndex != 0) { + totalSeekCount += abs(currentPosition - requestArray[0]); + currentPosition = requestArray[0]; + processedCount = 1; // 重置为1,因为重新开始扫描 + } + + // 然后处理磁头下方的请求 + for (int i = 0; i < startIndex; i++) { + totalSeekCount += abs(currentPosition - requestArray[i]); + currentPosition = requestArray[i]; + outputArray[processedCount++] = currentPosition; + } + + // 计算平均寻道数 + return (double)totalSeekCount / requestLength; +} +``` + +该部分测试输出为: + +```txt +CSCAN Algorithm: +Visit Order: 50 54 54 54 63 64 64 67 69 70 71 73 74 75 83 89 89 90 93 94 96 101 102 102 103 107 109 110 110 112 114 118 119 121 121 121 129 132 137 138 142 144 144 146 151 151 151 153 155 157 158 166 167 168 173 173 175 175 176 176 177 179 180 182 186 188 189 189 191 192 193 193 195 197 197 197 1 4 11 12 12 21 24 25 25 26 27 27 29 31 31 31 32 33 33 41 45 47 48 49 +Avg Seek Times: 3.91 +``` + +循环扫描(CSCAN)算法类似于 SCAN 算法,但当磁头到达一个方向的最远端后,它会直接跳转到起始端,而不是改变方向。这种方式使得磁盘的磁头像循环一样移动,避免了SCAN算法中的反向移动。代码先处理磁头上方的请求,然后直接跳到最低的请求继续处理,直至结束。 + +CSCAN 提供了比 SCAN 更均匀的服务。由于磁头总是在一个方向上移动,所以它可以在相对固定的时间间隔内覆盖整个磁盘。尽管CSCAN可能有时候会导致更长的寻道距离(因为需要跳转到起点),但它保证了所有请求都会在有限时间内得到服务,从而减少了饥饿问题。CSCAN特别适用于请求分布不均匀的情况。它通过固定的方向移动,确保了即使在分布不均的情况下,每个请求最终都会被处理。尽管平均寻道时间可能略高,但它在保证服务公平性和预测性方面的优势使其成为许多场景下的理想选择。 + + + +## 2.5. FSCAN + +```c +void processRequests(int* requestArray, int requestLength, int* currentPosition, int* totalSeekCount, int* outputArray, int* processedCount) { + // 使用 qsort 进行排序 + qsort(requestArray, requestLength, sizeof(int), compare); + + // 处理所有请求 + for (int i = 0; i < requestLength; i++) { + *totalSeekCount += abs(*currentPosition - requestArray[i]); + *currentPosition = requestArray[i]; + outputArray[(*processedCount)++] = requestArray[i]; + } +} + +double fscan(int* requestArray1, int requestLength1, int* requestArray2, int requestLength2, int headStart, int *outputArray) { + int totalSeekCount = 0; + int currentPosition = headStart; + int processedCount = 0; + + outputArray = (int*)malloc(sizeof(int) * (requestLength1 + requestLength2)); + + // 处理第一个队列 + processRequests(requestArray1, requestLength1, ¤tPosition, &totalSeekCount, outputArray, &processedCount); + + // 处理第二个队列 + processRequests(requestArray2, requestLength2, ¤tPosition, &totalSeekCount, outputArray, &processedCount); + + // 计算平均寻道数 + return (double)totalSeekCount / (requestLength1 + requestLength2); +} +``` + +该部分测试输出为: + +```txt +FSCAN Algorithm: +Visit Order: 50 54 54 54 63 64 69 70 74 75 89 90 101 103 107 109 110 112 118 121 121 129 138 144 151 166 168 175 176 176 177 179 180 186 189 191 192 193 49 48 32 31 31 29 26 25 25 24 12 11 158 166 167 168 173 173 175 175 176 176 177 179 180 182 186 188 189 189 191 192 193 193 195 197 197 197 1 4 11 12 12 21 24 25 25 26 27 27 29 31 31 31 32 33 33 41 45 47 48 49 +Avg Seek Times: 7.07 +``` + +FSCAN通过将请求分成两个队列来减少磁头移动次数。这样,它可以在处理当前队列时不受新请求的干扰。由于FSCAN总是处理完整个队列,它可以有效预防饥饿问题。每个请求最终都会被处理。分开处理两个队列可以提高处理效率。在处理一个队列时,磁头不需要反复移动以应对新进的请求。 + +测试输出显示平均寻道时间为7.07。这个数值相比其他算法较高,可能是因为在处理一个队列时,磁头需要移动到另一个队列的起始位置,导致额外的寻道时间。 + +FSCAN特别适合于请求量大且需要公平处理的场景。它通过分开处理不同的请求队列,确保了长期运行下的稳定性和公平性。 + + + +## 2.5. MAIN + +```c +void processQueue(int* queue, int length, int* currentPosition, int* totalSeekCount, int* outputArray, int* processedCount) { + // 对队列进行排序 + qsort(queue, length, sizeof(int), compare); + + // 找到最接近当前磁头位置的请求 + int i = 0; + while (i < length && queue[i] < *currentPosition) { + i++; + } + + // 先处理磁头位置之后(更高磁道号)的请求 + for (int j = i; j < length; j++) { + *totalSeekCount += abs(*currentPosition - queue[j]); + *currentPosition = queue[j]; + outputArray[(*processedCount)++] = queue[j]; + } + + // 再处理磁头位置之前(更低磁道号)的请求 + for (int j = i - 1; j >= 0; j--) { + *totalSeekCount += abs(*currentPosition - queue[j]); + *currentPosition = queue[j]; + outputArray[(*processedCount)++] = queue[j]; + } +} + +double fscan(int* requestArray1, int requestLength1, int* requestArray2, int requestLength2, int headStart, int* outputArray) { + int totalSeekCount = 0; + int currentPosition = headStart; + int processedCount = 0; + + // 处理第一个队列 + processQueue(requestArray1, requestLength1, ¤tPosition, &totalSeekCount, outputArray, &processedCount); + + // 处理第二个队列 + processQueue(requestArray2, requestLength2, ¤tPosition, &totalSeekCount, outputArray + requestLength1, &processedCount); + + // 计算平均寻道数 + return (double)totalSeekCount / (requestLength1 + requestLength2); +} +``` + + + +## 结论 + +| 算法 | 平均寻道数1 | 平均寻道数2 | +| ----- | ----------- | ----------- | +| FCFS | 66.65 | 60.75 | +| SSTF | 2.45 | 2.49 | +| SCAN | 3.43 | 3.48 | +| CSCAN | 3.91 | 3.97 | +| FSCAN | 7.07 | 7.34 | + +从实验数据中,我们可以看到不同磁盘调度算法在平均寻道数方面的表现差异。以下是对各算法性能的总结和分析: + +**FCFS (先来先服务)** + +- **平均寻道数:** 66.65 和 60.75 +- **分析:** FCFS算法因其简单性在平均寻道数上表现最差。由于它仅基于请求到达的顺序,没有考虑磁头移动的最优化,因此导致了较高的寻道数。 + +**SSTF (最短寻道时间优先)** + +- **平均寻道数:** 2.45 和 2.49 +- **分析:** SSTF算法大幅改进了寻道性能,因为它总是选择距离当前磁头位置最近的请求。这种策略显著减少了磁头的移动距离,从而降低了平均寻道数。 + +**SCAN (扫描)** + +- **平均寻道数:** 3.43 和 3.48 +- **分析:** SCAN算法的表现优于FCFS,略逊于SSTF。它通过模仿电梯运行,从一端移动到另一端,避免了频繁的方向改变,从而实现了较低的寻道数。 + +**CSCAN (循环扫描)** + +- **平均寻道数:** 3.91 和 3.97 +- **分析:** CSCAN算法提供了一个循环的扫描机制,避免了SCAN中的反向移动,但其平均寻道数略高于SCAN。这可能是因为在达到一端后需要跳转到另一端,导致了额外的寻道时间。 + +**FSCAN (FIFO扫描)** + +- **平均寻道数:** 7.07 和 7.34 +- **分析:** FSCAN算法在所有测试算法中平均寻道数次高。虽然它通过使用两个队列来优化长期的公平性和稳定性,但在单次运行中,由于需要在两个队列间切换,可能导致较高的寻道数。 + +**总体观察** + +- 在这些算法中,**SSTF** 和 **SCAN** 在平均寻道数方面表现最佳,提供了较好的性能与效率平衡。 +- **FCFS** 由于其简单性,在寻道数方面表现最差。 +- **CSCAN** 和 **FSCAN** 虽然在某些场景下可能更合适,但在这组数据中它们的平均寻道数相对较高。 + +这些结果显示了在不同的磁盘调度策略下,性能和效率之间的权衡。选择最合适的算法取决于具体的应用场景和对性能、效率以及公平性的不同需求。 diff --git a/Lab/Lab6/21281280-柯劲帆-第6次实验.pdf b/Lab/Lab6/21281280-柯劲帆-第6次实验.pdf new file mode 100644 index 0000000..83dfb70 Binary files /dev/null and b/Lab/Lab6/21281280-柯劲帆-第6次实验.pdf differ diff --git a/Lab/Lab6/code/KJF_disk.img b/Lab/Lab6/code/KJF_disk.img new file mode 100644 index 0000000..b46450b Binary files /dev/null and b/Lab/Lab6/code/KJF_disk.img differ diff --git a/Lab/Lab6/code/data.txt b/Lab/Lab6/code/data.txt new file mode 100644 index 0000000..f787424 --- /dev/null +++ b/Lab/Lab6/code/data.txt @@ -0,0 +1,3 @@ +x86 (also known as 80x86 or the 8086 family) is a family of complex instruction set computer (CISC) instruction set architectures initially developed by Intel based on the Intel 8086 microprocessor and its 8088 variant. The 8086 was introduced in 1978 as a fully 16-bit extension of Intel's 8-bit 8080 microprocessor, with memory segmentation as a solution for addressing more memory than can be covered by a plain 16-bit address. The term "x86" came into being because the names of several successors to Intel's 8086 processor end in "86", including the 80186, 80286, 80386 and 80486 processors. +The Intel x86 processor uses complex instruction set computer (CISC) architecture, which means there is a modest number of special-purpose registers instead of large quantities of general-purpose registers. It also means that complex special-purpose instructions will predominate. +The x86 processor traces its heritage at least as far back as the 8-bit Intel 8080 processor. Many peculiarities in the x86 instruction set are due to the backward compatibility with that processor (and with its Zilog Z-80 variant). \ No newline at end of file diff --git a/Lab/Lab6/code/main.c b/Lab/Lab6/code/main.c new file mode 100644 index 0000000..2af62e2 --- /dev/null +++ b/Lab/Lab6/code/main.c @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include + +#define BS_jmpBoot_Offset 0x00 // 跳转指令偏移 +#define BS_jmpBoot_Len 3 // 跳转指令长度 +#define BS_OEMName_Offset 0x03 // 厂商名称偏移 +#define BS_OEMName_Len 8 // 厂商名称长度 +#define BPB_BytsPerSec_Offset 0x0B // 每扇区字节数偏移 +#define BPB_BytsPerSec_Len 2 // 每扇区字节数长度 +#define BPB_SecPerClus_Offset 0x0D // 每簇扇区数偏移 +#define BPB_SecPerClus_Len 1 // 每簇扇区数长度 +#define BPB_RsvdSecCnt_Offset 0x0E // 保留扇区数 (引导扇区的扇区数)偏移 +#define BPB_RsvdSecCnt_Len 2 // 保留扇区数 (引导扇区的扇区数)长度 +#define BPB_NumFATs_Offset 0x10 // FAT的份数偏移 +#define BPB_NumFATs_Len 1 // FAT的份数长度 +#define BPB_RootEntCnt_Offset 0x11 // 根目录可容纳的目录项数偏移 +#define BPB_RootEntCnt_Len 2 // 根目录可容纳的目录项数长度 +#define BPB_TotSec16_Offset 0x13 // 扇区总数偏移 +#define BPB_TotSec16_Len 2 // 扇区总数长度 +#define BPB_Media_Offset 0x15 // 介质描述符偏移 +#define BPB_Media_Len 1 // 介质描述符长度 +#define BPB_FATSz16_Offset 0x16 // 每个FAT表扇区数偏移 +#define BPB_FATSz16_Len 2 // 每个FAT表扇区数长度 +#define BPB_SecPerTrk_Offset 0x18 // 每磁道扇区数偏移 +#define BPB_SecPerTrk_Len 2 // 每磁道扇区数长度 +#define BPB_NumHeads_Offset 0x1A // 磁头数偏移 +#define BPB_NumHeads_Len 2 // 磁头数长度 +#define BPB_HiddSec_Offset 0x1C // 隐藏扇区数偏移 +#define BPB_HiddSec_Len 4 // 隐藏扇区数长度 +#define BPB_TotSec32_Offset 0x20 // 偏移 如果BPB_TotSec16是0,由这个值记录扇区数 +#define BPB_TotSec32_Len 4 // 长度 如果BPB_TotSec16是0,由这个值记录扇区数 +#define BS_DrvNum_Offset 0x24 // int 13h的驱动器号偏移 +#define BS_DrvNum_Len 1 // int 13h的驱动器号长度 +#define BS_Reservedl_Offset 0x25 // 偏移 未使用 +#define BS_Reservedl_Len 1 // 长度 未使用 +#define BS_BootSig_Offset 0x26 // 扩展引导标记偏移 +#define BS_BootSig_Len 1 // 扩展引导标记长度 +#define BS_VolID_Offset 0x27 // 卷序列号偏移 +#define BS_VolID_Len 4 // 卷序列号长度 +#define BS_VolLab_Offset 0x2B // 卷标偏移 +#define BS_VolLab_Len 11 // 卷标长度 +#define BS_FileSysType_Offset 0x36 // 文件系统类型偏移 +#define BS_FileSysType_Len 8 // 文件系统类型长度 +#define DBR_BootCode_Offset 0x3E // 引导代码及其他偏移 +#define DBR_BootCode_Len 448 // 引导代码及其他长度 +#define DBR_EndMark_Offset 0x1FE // 结束标志偏移 +#define DBR_EndMark_Len 2 // 结束标志长度 +#define DBR_EndMark 0xAA55 // 结束标志 + +typedef struct { + short BytsPerSec; // 每扇区字节数 + char SecPerClus; // 每簇扇区数 + short RsvdSecCnt; // 保留扇区数 (引导扇区的扇区数) + char NumFATs; // FAT的份数 + short RootEntCnt; // 根目录可容纳的目录项数 + short TotSec16; // 扇区总数 + char Media; // 介质描述符 + short FATSz16; // 每个FAT表扇区数 + short SecPerTrk; // 每磁道扇区数 + short NumHeads; // 磁头数 + int HiddSec; // 隐藏扇区数 + int TotSec32; // 替代扇区数 + char DrvNum; // int 13h的驱动器号 + char Reservedl; // 未使用 + char BootSig; // 扩展引导标记 + int VolID; // 卷序列号 + int TotalSize; // 总磁盘大小 + int current_dir; // 当前目录的首地址 +} DISK; + + +DISK init_disk_attr() { + DISK disk; + disk.BytsPerSec = 0x0200; + disk.SecPerClus = 0x01; + disk.RsvdSecCnt = 0x0001; + disk.NumFATs = 0x01; + disk.RootEntCnt = 0x0200; + disk.TotSec16 = 0x2000; + disk.Media = 0xf8; + disk.FATSz16 = 0x0020; + disk.SecPerTrk = 0x0020; + disk.NumHeads = 0x0040; + disk.HiddSec = 0x00000000; + disk.TotSec32 = 0x00000000; + disk.DrvNum = 0x80; + disk.Reservedl = 0x00; + disk.BootSig = 0x29; + disk.VolID = 0x00000000; + disk.TotalSize = 0x400000; + disk.current_dir = 0x4200; + return disk; +} + + +struct tm get_time() { + struct timeval tv; + struct timezone tz; + struct tm *t; + + gettimeofday(&tv, &tz); + t = localtime(&tv.tv_sec); + return *t; +} + + + +void init_file_system(char disk_name[13], DISK disk_attr) { + FILE* fp = fopen(disk_name, "wb"); + // 跳转指令 + fseek(fp, BS_jmpBoot_Offset, 0); + char jmpBoot[BS_jmpBoot_Len] = { 0xEB, 0x3C, 0x90 }; + fwrite(jmpBoot, 1, BS_jmpBoot_Len, fp); + + // 厂商名称 + fseek(fp, BS_OEMName_Offset, 0); + char OEMName[BS_OEMName_Len] = { 'K', 'e', 'J', 'F', ' ', ' ', ' ', ' ' }; + fwrite(OEMName, 1, BS_OEMName_Len, fp); + + // 每扇区字节数 + fseek(fp, BPB_BytsPerSec_Offset, 0); + fwrite(&disk_attr.BytsPerSec, BPB_BytsPerSec_Len, 1, fp); + + // 每簇扇区数 + fseek(fp, BPB_SecPerClus_Offset, 0); + fwrite(&disk_attr.SecPerClus, BPB_SecPerClus_Len, 1, fp); + + // 保留扇区数 (引导扇区的扇区数) + fseek(fp, BPB_RsvdSecCnt_Offset, 0); + fwrite(&disk_attr.RsvdSecCnt, BPB_RsvdSecCnt_Len, 1, fp); + + // FAT的份数 + fseek(fp, BPB_NumFATs_Offset, 0); + fwrite(&disk_attr.NumFATs, BPB_NumFATs_Len, 1, fp); + + // 根目录可容纳的目录项数 + fseek(fp, BPB_RootEntCnt_Offset, 0); + fwrite(&disk_attr.RootEntCnt, BPB_RootEntCnt_Len, 1, fp); + + // 扇区总数 + fseek(fp, BPB_TotSec16_Offset, 0); + fwrite(&disk_attr.TotSec16, BPB_TotSec16_Len, 1, fp); + + // 介质描述符 + fseek(fp, BPB_Media_Offset, 0); + fwrite(&disk_attr.Media, BPB_Media_Len, 1, fp); + + // 每个FAT表扇区数 + fseek(fp, BPB_FATSz16_Offset, 0); + fwrite(&disk_attr.FATSz16, BPB_FATSz16_Len, 1, fp); + + // 每磁道扇区数 + fseek(fp, BPB_SecPerTrk_Offset, 0); + fwrite(&disk_attr.SecPerTrk, BPB_SecPerTrk_Len, 1, fp); + + // 磁头数 + fseek(fp, BPB_NumHeads_Offset, 0); + fwrite(&disk_attr.NumHeads, BPB_NumHeads_Len, 1, fp); + + // 隐藏扇区数 + fseek(fp, BPB_HiddSec_Offset, 0); + fwrite(&disk_attr.HiddSec, BPB_HiddSec_Len, 1, fp); + + // 替代扇区数 + fseek(fp, BPB_TotSec32_Offset, 0); + fwrite(&disk_attr.TotSec32, BPB_TotSec32_Len, 1, fp); + + // int 13h的驱动器号 + fseek(fp, BS_DrvNum_Offset, 0); + fwrite(&disk_attr.DrvNum, BS_DrvNum_Len, 1, fp); + + // 未使用 + fseek(fp, BS_Reservedl_Offset, 0); + fwrite(&disk_attr.Reservedl, BS_Reservedl_Len, 1, fp); + + // 扩展引导标记 + fseek(fp, BS_BootSig_Offset, 0); + fwrite(&disk_attr.BootSig, BS_BootSig_Len, 1, fp); + + // 卷序列号 + fseek(fp, BS_VolID_Offset, 0); + fwrite(&disk_attr.VolID, BS_VolID_Len, 1, fp); + + // 卷标 + fseek(fp, BS_VolLab_Offset, 0); + char VolLab[BS_VolLab_Len] = {'K', 'e', 'J', 'i', 'n', 'g', 'f', 'a', 'n', ' ', ' '}; + fwrite(VolLab, 1, BS_VolLab_Len, fp); + + // 文件系统类型 + fseek(fp, BS_FileSysType_Offset, 0); + char FileSysType[BS_FileSysType_Len] = {'F', 'A', 'T', '1', '6', ' ', ' ', ' '}; + fwrite(FileSysType, 1, BS_FileSysType_Len, fp); + + // 引导代码等 + fseek(fp, DBR_BootCode_Offset, 0); + char BootCode[DBR_BootCode_Len] = { 0 }; + fwrite(BootCode, 1, DBR_BootCode_Len, fp); + + // 结束标志 + fseek(fp, DBR_EndMark_Offset, 0); + short EndMark = DBR_EndMark; + fwrite(&EndMark, DBR_EndMark_Len, 1, fp); + + // FAT分区表 + fseek(fp, 0x0200, 0); + int FAT_Len = disk_attr.FATSz16 * disk_attr.BytsPerSec * disk_attr.NumFATs; // 每个FAT表扇区数 * 扇区字节数 * FAT的份数 + char* FAT_data = (char*)malloc(sizeof(char) * FAT_Len); + fwrite(FAT_data, 1, FAT_Len, fp); + + // DATA段 + fseek(fp, 0x0200 + FAT_Len, 0); + int DATA_Len = disk_attr.TotalSize - 0x0200 - FAT_Len; + char* DATA_data = (char*)malloc(sizeof(char) * DATA_Len); + fwrite(DATA_data, 1, DATA_Len, fp); + + fclose(fp); +} + + +unsigned short find_free_blocks(DISK disk_attr, FILE *fp) { + unsigned short begin = 0x200; + unsigned short end = disk_attr.FATSz16 * disk_attr.BytsPerSec; + unsigned short ans = 0x06; + while (ans < end) { + fseek(fp, begin + ans, 0); + char index[5] = { 0 }; + fread(index, 1, 4, fp); + if (!index[0] && !index[1] && !index[2] && !index[3]) break; + ans += 2; + } + if (ans >= end) return 0xFFFF; + return ans / 2; +} + +int mkdir(char dir_name[11], char disk_name[13], DISK disk_attr) { + FILE* fp = fopen(disk_name, "rb+"); + unsigned short dir_firstBlockNum = find_free_blocks(disk_attr, fp); + if (dir_firstBlockNum == 0xFFFF) return -1; + + fseek(fp, 0x200 + dir_firstBlockNum * 2, 0); + short new_index = 0xFFFF; + fwrite(&new_index, 2, 1, fp); + + int start_byte = disk_attr.current_dir; + int byte_line_num = 0; + while (byte_line_num < disk_attr.BytsPerSec) { + fseek(fp, start_byte + byte_line_num, 0); + int byte_line[4] = { 0 }; + fread(byte_line, 4, 3, fp); + if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break; + byte_line_num += 0x20; + } + if (byte_line_num >= disk_attr.BytsPerSec) return -1; + fseek(fp, start_byte + byte_line_num, 0); + fwrite(dir_name, 1, 11, fp); + short dir_attr_byte = 0b00010000; + fwrite(&dir_attr_byte, 1, 1, fp); + + short dir_reserved_byte[6] = { 0 }; + fwrite(dir_reserved_byte, 2, 5, fp); + + struct tm time = get_time(); + short dir_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2; + fwrite(&dir_time_byte, 2, 1, fp); + short dir_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday; + fwrite(&dir_date_byte, 2, 1, fp); + fwrite(&dir_firstBlockNum, 2, 1, fp); + short dir_size = disk_attr.BytsPerSec * disk_attr.SecPerClus; + fwrite(&dir_size, 2, 1, fp); + + int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * dir_firstBlockNum; + fseek(fp, new_block, 0); + char dot[11] = { '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + fwrite(dot, 1, 11, fp); + fwrite(&dir_attr_byte, 1, 1, fp); + fwrite(dir_reserved_byte, 2, 5, fp); + fwrite(&dir_time_byte, 2, 1, fp); + fwrite(&dir_date_byte, 2, 1, fp); + short dir_oriBlock_byte = 0; + fwrite(&dir_oriBlock_byte, 2, 1, fp); + unsigned int dir_oriSize = 0; + fwrite(&dir_oriSize, 4, 1, fp); + + fseek(fp, new_block + 0x20, 0); + char dotdot[11] = { '.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + fwrite(dotdot, 1, 11, fp); + fwrite(&dir_attr_byte, 1, 1, fp); + fwrite(dir_reserved_byte, 2, 5, fp); + short dir_oriTime_byte = 0; + fwrite(&dir_oriTime_byte, 2, 1, fp); + short dir_oriDate_byte = (1970 - 1980 + 1900) * 512 + 1 * 32 + 1; + fwrite(&dir_oriDate_byte, 2, 1, fp); + fwrite(&dir_oriBlock_byte, 2, 1, fp); + fwrite(&dir_oriSize, 4, 1, fp); + + fclose(fp); + return 0; +} + + +int touch(char file_name[11], char disk_name[13], DISK disk_attr) { + FILE* contentFile = fopen("./data.txt", "r"); + char *content = (char*)malloc(2000 * sizeof(char)); + fread(content, 1, 2000, contentFile); + fclose(contentFile); + FILE* fp = fopen(disk_name, "rb+"); + int file_blockNum = strlen(content) / (disk_attr.BytsPerSec * disk_attr.SecPerClus) + 1; + unsigned short* index = (short*)malloc(sizeof(short) * file_blockNum); + for (int i = 0; i < file_blockNum; i++) { + index[i] = find_free_blocks(disk_attr, fp); + } + for (int i = 0; i < file_blockNum - 1; i++) { + fseek(fp, 0x200 + index[i] * 2, 0); + fwrite(&index[i + 1], 2, 1, fp); + } + fseek(fp, 0x200 + index[file_blockNum - 1] * 2, 0); + short last_index = 0xFFFF; + fwrite(&last_index, 2, 1, fp); + + int start_byte = disk_attr.current_dir; + int byte_line_num = 0; + while (byte_line_num < disk_attr.BytsPerSec) { + fseek(fp, start_byte + byte_line_num, 0); + int byte_line[4] = { 0 }; + fread(byte_line, 4, 3, fp); + if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break; + byte_line_num += 0x20; + } + if (byte_line_num >= disk_attr.BytsPerSec) return -1; + fseek(fp, start_byte + byte_line_num, 0); + fwrite(file_name, 1, 11, fp); + short file_attr_byte = 0b00000000; + fwrite(&file_attr_byte, 1, 1, fp); + + short file_reserved_byte[6] = { 0 }; + fwrite(file_reserved_byte, 2, 5, fp); + + struct tm time = get_time(); + short file_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2; + fwrite(&file_time_byte, 2, 1, fp); + short file_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday; + fwrite(&file_date_byte, 2, 1, fp); + fwrite(&index[0], 2, 1, fp); + unsigned int file_size = strlen(content); + fwrite(&file_size, 4, 1, fp); + + for (int i = 0; i < file_blockNum; i++) { + int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * index[i]; + fseek(fp, new_block, 0); + fwrite(content, + file_size > disk_attr.BytsPerSec * disk_attr.SecPerClus ? disk_attr.BytsPerSec * disk_attr.SecPerClus : file_size, + 1, + fp + ); + content += disk_attr.BytsPerSec * disk_attr.SecPerClus; + file_size -= disk_attr.BytsPerSec * disk_attr.SecPerClus; + } + fclose(fp); + return 0; +} + + +int main() { + char disk_name[13] = "KJF_disk.img"; + DISK disk_attr = init_disk_attr(); + init_file_system(disk_name, disk_attr); + + // char dir_name[11] = "testdir "; + // mkdir(dir_name, disk_name, disk_attr); + // char file_name[11] = "data txt"; + // touch(file_name, disk_name, disk_attr); + + return 0; +} + diff --git a/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题20_FAT文件系统模拟设计与实现》.pdf b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题20_FAT文件系统模拟设计与实现》.pdf new file mode 100644 index 0000000..f801747 Binary files /dev/null and b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题20_FAT文件系统模拟设计与实现》.pdf differ diff --git a/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题21_基于Linux的类EXT文件系统的克隆实现》.pdf b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题21_基于Linux的类EXT文件系统的克隆实现》.pdf new file mode 100644 index 0000000..457396d Binary files /dev/null and b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题21_基于Linux的类EXT文件系统的克隆实现》.pdf differ diff --git a/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题22_Linux特定文件系统设计探析》.pdf b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题22_Linux特定文件系统设计探析》.pdf new file mode 100644 index 0000000..a760edf Binary files /dev/null and b/Lab/Lab6/requirements/ZGSOS操作系统实验指导《实验课题22_Linux特定文件系统设计探析》.pdf differ diff --git a/Lab/Lab6/source/21281280-柯劲帆-第6次实验.md b/Lab/Lab6/source/21281280-柯劲帆-第6次实验.md new file mode 100644 index 0000000..313e22b --- /dev/null +++ b/Lab/Lab6/source/21281280-柯劲帆-第6次实验.md @@ -0,0 +1,492 @@ +

北京交通大学实验报告

+ +
+
课程名称:操作系统
+
实验题目:FAT文件系统模拟与实现
+
学号:21281280
+
姓名:柯劲帆
+
班级:物联网2101班
+
指导老师:何永忠
+
报告日期:2023年12月31日
+
+ + + + + +--- + +## 目录 + +[TOC] + +--- + + + +# 1. 开发运行环境和工具 + +| 操作系统 | Linux内核版本 | 处理器 | GCC版本 | +| :---------: | :-----------------------: | :--------------------------------------: | :-------------------------------: | +| Deepin 20.9 | 5.18.17-amd64-desktop-hwe | Intel(R) Core(TM) i3-2330M CPU @ 2.20GHz | gcc (Uos 8.3.0.3-3+rebuild) 8.3.0 | + + - **其他工具**: + - **编辑**:VSCode + - **编译和运行**:Terminal + + + +# 2. FAT磁盘格式化操作和映像文件生成 + +FAT的引导扇区各个字段的定义和说明如下: + +| 字节位移 | 字段长度(字节) | 字段名称 | +| -------- | -------------- | ---------------------------- | +| 0x00 | 3 | 跳转指令(Jump Instruction) | +| 0x03 | 8 | OEM ID | +| 0x0B | 25 | BPB | +| 0x24 | 26 | 扩展BPB | +| 0x3E | 448 | 引导程序代码(Bootstrap Code) | +| 0x01FE | 4 | 扇区结束标识符(0xAA55) | + +FAT16分区的BPB字段为: + +| 字节位移 | 字段长度(字节) | 例值 | 名称、定义和描述 | +| -------- | -------------- | ----------- | ------------------------------------------------------------ | +| 0x0B | 2 | 0x0200 | 扇区字节数(Bytes Per Sector) 硬件扇区的大小。本字段合法的十进制值有512、1024、2048和4096。对大多数磁盘来说,本字段的值为512 | +| 0x0D | 1 | 0x10 | 每簇扇区数(Sectors Per Cluster) 一个簇中的扇区数。由于FAT16文件系统只能跟踪有限个簇(最多为65536个)。因此,通过增加每簇的扇区数可以支持最大分区数。分区的缺省的簇的大小取决于该分区的大小。本字段合法的十进制值有 1、2、4、8、16、32、64和128。导致簇大于32KB(每扇区字节数*每簇扇区数)的值会引起磁盘错误和软件错误 | +| 0x0e | 2 | 0x0006 | 保留扇区数(Reserved Sector) 第一个FAT开始之前的扇区数,包括引导扇区。 | +| 0x10 | 1 | 0x02 | FAT数(Number of FAT)该分区上FAT的副本数。本字段的值一般为2 | +| 0x11 | 2 | 0x0200 | 根目录项数(Root Entries) 能够保存在该分区的根目录文件夹中的32个字节长的文件和文件夹名称项的总数。在一个典型的硬盘上,本字段的值为512。其中一个项常常被用作卷标号(Volume Label),长名称的文件和文件夹每个文件使用多个项。文件和文件夹项的最大数一般为511,但是如果使用的长文件名,往往都达不到这个数。 | +| 0x13 | 2 | 0x0000 | 小扇区数(Small Sector) 该分区上的扇区数,表示为16位(<65536)。对大于65536个扇区的分区来说,本字段的值为0,而使用大扇区数来取代它。 | +| 0x15 | 1 | 0xF8 | 媒体描述符( Media Descriptor)提供有关媒体被使用的信息。值0xF8表示硬盘,0xF0表示高密度的3.5寸软盘。媒体描述符要用于MS-DOS FAT16磁盘,在Windows 2000中未被使用 | +| 0x16 | 2 | 0x00F5 | 每FAT扇区数(Sectors Per FAT) 该分区上每个FAT所占用的扇区数。计算机利用这个数和FAT数以及隐藏扇区数来决定根目录在哪里开始。计算机还可以根据根目录中的项数(512)决定该分区的用户数据区从哪里开始 | +| 0x18 | 2 | 0x003F | 每道扇区数(Sectors Per Trark) | +| 0x1A | 2 | 0x00FF | 磁头数(Number of head) | +| 0x1C | 4 | 0x000000400 | 隐藏扇区数(Hidden Sector) 该分区上引导扇区之前的扇区数。在引导序列计算到根目录和数据区的绝对位移的过程中使用了该值 | +| 0x20 | 4 | 0x000F4C00 | 大扇区数(Large Sector) 如果小扇区数字段的值为0,本字段就包含该FAT16分区中的总扇区数。如果小扇区数字段的值不为0,那么本字段的值为0 | + +FAT16分区的扩展BPB字段为: + +| 字节位移 | 字段长度(字节) | 图8对应取值 | 名称、定义和描述 | +| -------- | -------------- | ----------- | ------------------------------------------------------------ | +| 0x24 | 1 | 0x80 | 物理驱动器号( Physical Drive Number) 与BIOS物理驱动器号有关。软盘驱动器被标识为0x00,物理硬盘被标识为0x80,而与物理磁盘驱动器无关。一般地,在发出一个INT13h BIOS调用之前设置该值,具体指定所访问的设备。只有当该设备是一个引导设备时,这个值才有意义 | +| 0x25 | 1 | 0x01 | 保留(Reserved) FAT16分区一般将本字段的值设置为1 | +| 0x26 | 1 | 0x29 | 扩展引导标签(Extended Boot Signature) 本字段必须要有能被Windows 2000所识别的值0x28或0x29 | +| 0x27 | 2 | 0xABA13358 | 卷序号(Volume Serial Number) 在格式化磁盘时所产生的一个随机序号,它有助于区分磁盘 | +| 0x2B | 11 | "NO NAME" | 卷标(Volume Label) 本字段只能使用一次,它被用来保存卷标号。现在,卷标被作为一个特殊文件保存在根目录中 | +| 0x36 | 8 | "FAT16" | 文件系统类型(File System Type) 根据该磁盘格式,该字段的值可以为FAT、FAT12或FAT16 | + +因此,各参数初始化如下: + +```c +#include +#include +#include +#include +#include + +#define BS_jmpBoot_Offset 0x00 // 跳转指令偏移 +#define BS_jmpBoot_Len 3 // 跳转指令长度 +#define BS_OEMName_Offset 0x03 // 厂商名称偏移 +#define BS_OEMName_Len 8 // 厂商名称长度 +#define BPB_BytsPerSec_Offset 0x0B // 每扇区字节数偏移 +#define BPB_BytsPerSec_Len 2 // 每扇区字节数长度 +#define BPB_SecPerClus_Offset 0x0D // 每簇扇区数偏移 +#define BPB_SecPerClus_Len 1 // 每簇扇区数长度 +#define BPB_RsvdSecCnt_Offset 0x0E // 保留扇区数 (引导扇区的扇区数)偏移 +#define BPB_RsvdSecCnt_Len 2 // 保留扇区数 (引导扇区的扇区数)长度 +#define BPB_NumFATs_Offset 0x10 // FAT的份数偏移 +#define BPB_NumFATs_Len 1 // FAT的份数长度 +#define BPB_RootEntCnt_Offset 0x11 // 根目录可容纳的目录项数偏移 +#define BPB_RootEntCnt_Len 2 // 根目录可容纳的目录项数长度 +#define BPB_TotSec16_Offset 0x13 // 扇区总数偏移 +#define BPB_TotSec16_Len 2 // 扇区总数长度 +#define BPB_Media_Offset 0x15 // 介质描述符偏移 +#define BPB_Media_Len 1 // 介质描述符长度 +#define BPB_FATSz16_Offset 0x16 // 每个FAT表扇区数偏移 +#define BPB_FATSz16_Len 2 // 每个FAT表扇区数长度 +#define BPB_SecPerTrk_Offset 0x18 // 每磁道扇区数偏移 +#define BPB_SecPerTrk_Len 2 // 每磁道扇区数长度 +#define BPB_NumHeads_Offset 0x1A // 磁头数偏移 +#define BPB_NumHeads_Len 2 // 磁头数长度 +#define BPB_HiddSec_Offset 0x1C // 隐藏扇区数偏移 +#define BPB_HiddSec_Len 4 // 隐藏扇区数长度 +#define BPB_TotSec32_Offset 0x20 // 偏移 如果BPB_TotSec16是0,由这个值记录扇区数 +#define BPB_TotSec32_Len 4 // 长度 如果BPB_TotSec16是0,由这个值记录扇区数 +#define BS_DrvNum_Offset 0x24 // int 13h的驱动器号偏移 +#define BS_DrvNum_Len 1 // int 13h的驱动器号长度 +#define BS_Reservedl_Offset 0x25 // 偏移 未使用 +#define BS_Reservedl_Len 1 // 长度 未使用 +#define BS_BootSig_Offset 0x26 // 扩展引导标记偏移 +#define BS_BootSig_Len 1 // 扩展引导标记长度 +#define BS_VolID_Offset 0x27 // 卷序列号偏移 +#define BS_VolID_Len 4 // 卷序列号长度 +#define BS_VolLab_Offset 0x2B // 卷标偏移 +#define BS_VolLab_Len 11 // 卷标长度 +#define BS_FileSysType_Offset 0x36 // 文件系统类型偏移 +#define BS_FileSysType_Len 8 // 文件系统类型长度 +#define DBR_BootCode_Offset 0x3E // 引导代码及其他偏移 +#define DBR_BootCode_Len 448 // 引导代码及其他长度 +#define DBR_EndMark_Offset 0x1FE // 结束标志偏移 +#define DBR_EndMark_Len 2 // 结束标志长度 +#define DBR_EndMark 0xAA55 // 结束标志 + +typedef struct { + short BytsPerSec; // 每扇区字节数 + char SecPerClus; // 每簇扇区数 + short RsvdSecCnt; // 保留扇区数 (引导扇区的扇区数) + char NumFATs; // FAT的份数 + short RootEntCnt; // 根目录可容纳的目录项数 + short TotSec16; // 扇区总数 + char Media; // 介质描述符 + short FATSz16; // 每个FAT表扇区数 + short SecPerTrk; // 每磁道扇区数 + short NumHeads; // 磁头数 + int HiddSec; // 隐藏扇区数 + int TotSec32; // 替代扇区数 + char DrvNum; // int 13h的驱动器号 + char Reservedl; // 未使用 + char BootSig; // 扩展引导标记 + int VolID; // 卷序列号 + int TotalSize; // 总磁盘大小 + int current_dir; // 当前目录的首地址 +} DISK; + + +DISK init_disk_attr() { + DISK disk; + disk.BytsPerSec = 0x0200; + disk.SecPerClus = 0x01; + disk.RsvdSecCnt = 0x0001; + disk.NumFATs = 0x01; + disk.RootEntCnt = 0x0200; + disk.TotSec16 = 0x2000; + disk.Media = 0xf8; + disk.FATSz16 = 0x0020; + disk.SecPerTrk = 0x0020; + disk.NumHeads = 0x0040; + disk.HiddSec = 0x00000000; + disk.TotSec32 = 0x00000000; + disk.DrvNum = 0x80; + disk.Reservedl = 0x00; + disk.BootSig = 0x29; + disk.VolID = 0x00000000; + disk.TotalSize = 0x400000; + disk.current_dir = 0x4200; + return disk; +} +``` + +初始化和格式化磁盘代码如下: + +```c +void init_file_system(char disk_name[13], DISK disk_attr) { + FILE* fp = fopen(disk_name, "wb"); + // 跳转指令 + fseek(fp, BS_jmpBoot_Offset, 0); + char jmpBoot[BS_jmpBoot_Len] = { 0xEB, 0x3C, 0x90 }; + fwrite(jmpBoot, 1, BS_jmpBoot_Len, fp); + + // 厂商名称 + fseek(fp, BS_OEMName_Offset, 0); + char OEMName[BS_OEMName_Len] = { 'K', 'e', 'J', 'F', ' ', ' ', ' ', ' ' }; + fwrite(OEMName, 1, BS_OEMName_Len, fp); + + // 每扇区字节数 + fseek(fp, BPB_BytsPerSec_Offset, 0); + fwrite(&disk_attr.BytsPerSec, BPB_BytsPerSec_Len, 1, fp); + + // 每簇扇区数 + fseek(fp, BPB_SecPerClus_Offset, 0); + fwrite(&disk_attr.SecPerClus, BPB_SecPerClus_Len, 1, fp); + + // 保留扇区数 (引导扇区的扇区数) + fseek(fp, BPB_RsvdSecCnt_Offset, 0); + fwrite(&disk_attr.RsvdSecCnt, BPB_RsvdSecCnt_Len, 1, fp); + + // FAT的份数 + fseek(fp, BPB_NumFATs_Offset, 0); + fwrite(&disk_attr.NumFATs, BPB_NumFATs_Len, 1, fp); + + // 根目录可容纳的目录项数 + fseek(fp, BPB_RootEntCnt_Offset, 0); + fwrite(&disk_attr.RootEntCnt, BPB_RootEntCnt_Len, 1, fp); + + // 扇区总数 + fseek(fp, BPB_TotSec16_Offset, 0); + fwrite(&disk_attr.TotSec16, BPB_TotSec16_Len, 1, fp); + + // 介质描述符 + fseek(fp, BPB_Media_Offset, 0); + fwrite(&disk_attr.Media, BPB_Media_Len, 1, fp); + + // 每个FAT表扇区数 + fseek(fp, BPB_FATSz16_Offset, 0); + fwrite(&disk_attr.FATSz16, BPB_FATSz16_Len, 1, fp); + + // 每磁道扇区数 + fseek(fp, BPB_SecPerTrk_Offset, 0); + fwrite(&disk_attr.SecPerTrk, BPB_SecPerTrk_Len, 1, fp); + + // 磁头数 + fseek(fp, BPB_NumHeads_Offset, 0); + fwrite(&disk_attr.NumHeads, BPB_NumHeads_Len, 1, fp); + + // 隐藏扇区数 + fseek(fp, BPB_HiddSec_Offset, 0); + fwrite(&disk_attr.HiddSec, BPB_HiddSec_Len, 1, fp); + + // 替代扇区数 + fseek(fp, BPB_TotSec32_Offset, 0); + fwrite(&disk_attr.TotSec32, BPB_TotSec32_Len, 1, fp); + + // int 13h的驱动器号 + fseek(fp, BS_DrvNum_Offset, 0); + fwrite(&disk_attr.DrvNum, BS_DrvNum_Len, 1, fp); + + // 未使用 + fseek(fp, BS_Reservedl_Offset, 0); + fwrite(&disk_attr.Reservedl, BS_Reservedl_Len, 1, fp); + + // 扩展引导标记 + fseek(fp, BS_BootSig_Offset, 0); + fwrite(&disk_attr.BootSig, BS_BootSig_Len, 1, fp); + + // 卷序列号 + fseek(fp, BS_VolID_Offset, 0); + fwrite(&disk_attr.VolID, BS_VolID_Len, 1, fp); + + // 卷标 + fseek(fp, BS_VolLab_Offset, 0); + char VolLab[BS_VolLab_Len] = {'K', 'e', 'J', 'i', 'n', 'g', 'f', 'a', 'n', ' ', ' '}; + fwrite(VolLab, 1, BS_VolLab_Len, fp); + + // 文件系统类型 + fseek(fp, BS_FileSysType_Offset, 0); + char FileSysType[BS_FileSysType_Len] = {'F', 'A', 'T', '1', '6', ' ', ' ', ' '}; + fwrite(FileSysType, 1, BS_FileSysType_Len, fp); + + // 引导代码等 + fseek(fp, DBR_BootCode_Offset, 0); + char BootCode[DBR_BootCode_Len] = { 0 }; + fwrite(BootCode, 1, DBR_BootCode_Len, fp); + + // 结束标志 + fseek(fp, DBR_EndMark_Offset, 0); + short EndMark = DBR_EndMark; + fwrite(&EndMark, DBR_EndMark_Len, 1, fp); + + // FAT分区表 + fseek(fp, 0x0200, 0); + int FAT_Len = disk_attr.FATSz16 * disk_attr.BytsPerSec * disk_attr.NumFATs; // 每个FAT表扇区数 * 扇区字节数 * FAT的份数 + char* FAT_data = (char*)malloc(sizeof(char) * FAT_Len); + fwrite(FAT_data, 1, FAT_Len, fp); + + // DATA段 + fseek(fp, 0x0200 + FAT_Len, 0); + int DATA_Len = disk_attr.TotalSize - 0x0200 - FAT_Len; + char* DATA_data = (char*)malloc(sizeof(char) * DATA_Len); + fwrite(DATA_data, 1, DATA_Len, fp); + + fclose(fp); +} +``` + +调用这个函数生成.img虚拟磁盘镜像挂载到linux上: + +```sh +> gcc ./main.c -o main +> ./main +> mkdir /media/cdrom +> sudo mount -o loop KJF_disk.img /media/cdrom +> cd /media/cdrom +``` + +发现可以正常认盘和使用。 + +查看其二进制: + +```txt +> hexdump KJF_disk.img -C +00000000 eb 3c 90 4b 65 4a 46 20 20 20 20 00 02 01 01 00 |.<.KeJF .....| +00000010 01 00 02 00 20 f8 20 00 20 00 40 00 00 00 00 00 |.... . . .@.....| +00000020 00 00 00 00 80 00 29 00 00 00 00 4b 65 4a 69 6e |......)....KeJin| +00000030 67 66 61 6e 20 20 46 41 54 31 36 20 20 20 00 00 |gfan FAT16 ..| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| +00000200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00400000 +``` + +FAT文件格式正确。 + + + +# 3. 目录 + +目录项字段定义: + +| 偏移地址 | 字节数 | 说明 | | +| --------- | ------ | ---------------- | ------------------------------------------------------------ | +| 0x00~0x07 | 8 | 文件名 | | +| 0x08~0x0A | 3 | 扩展名 | | +| 0x0B | 1 | 属 性 | 00000000(读写) 00000001(只读) 00000010(隐藏) 00000100(系统) 00001000(卷标)00010000(目录) 00100000(归档) | +| 0x0C~0x15 | 10 | 保留 | | +| 0x16~0x17 | 2 | 文件最近修改时间 | | +| 0x18~0x19 | 2 | 文件最近修改日期 | | +| 0x1A~0x1B | 2 | 文件的首簇号 | | +| 0x1C~0x1F | 4 | 文件长度大小 | | + +因此代码如下: + +```c +int mkdir(char dir_name[11], char disk_name[13], DISK disk_attr) { + FILE* fp = fopen(disk_name, "rb+"); + unsigned short dir_firstBlockNum = find_free_blocks(disk_attr, fp); + if (dir_firstBlockNum == 0xFFFF) return -1; + + fseek(fp, 0x200 + dir_firstBlockNum * 2, 0); + short new_index = 0xFFFF; + fwrite(&new_index, 2, 1, fp); + + int start_byte = disk_attr.current_dir; + int byte_line_num = 0; + while (byte_line_num < disk_attr.BytsPerSec) { + fseek(fp, start_byte + byte_line_num, 0); + int byte_line[4] = { 0 }; + fread(byte_line, 4, 3, fp); + if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break; + byte_line_num += 0x20; + } + if (byte_line_num >= disk_attr.BytsPerSec) return -1; + fseek(fp, start_byte + byte_line_num, 0); + fwrite(dir_name, 1, 11, fp); + short dir_attr_byte = 0b00010000; + fwrite(&dir_attr_byte, 1, 1, fp); + + short dir_reserved_byte[6] = { 0 }; + fwrite(dir_reserved_byte, 2, 5, fp); + + struct tm time = get_time(); + short dir_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2; + fwrite(&dir_time_byte, 2, 1, fp); + short dir_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday; + fwrite(&dir_date_byte, 2, 1, fp); + fwrite(&dir_firstBlockNum, 2, 1, fp); + short dir_size = disk_attr.BytsPerSec * disk_attr.SecPerClus; + fwrite(&dir_size, 2, 1, fp); + + int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * dir_firstBlockNum; + fseek(fp, new_block, 0); + char dot[11] = { '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + fwrite(dot, 1, 11, fp); + fwrite(&dir_attr_byte, 1, 1, fp); + fwrite(dir_reserved_byte, 2, 5, fp); + fwrite(&dir_time_byte, 2, 1, fp); + fwrite(&dir_date_byte, 2, 1, fp); + short dir_oriBlock_byte = 0; + fwrite(&dir_oriBlock_byte, 2, 1, fp); + unsigned int dir_oriSize = 0; + fwrite(&dir_oriSize, 4, 1, fp); + + fseek(fp, new_block + 0x20, 0); + char dotdot[11] = { '.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + fwrite(dotdot, 1, 11, fp); + fwrite(&dir_attr_byte, 1, 1, fp); + fwrite(dir_reserved_byte, 2, 5, fp); + short dir_oriTime_byte = 0; + fwrite(&dir_oriTime_byte, 2, 1, fp); + short dir_oriDate_byte = (1970 - 1980 + 1900) * 512 + 1 * 32 + 1; + fwrite(&dir_oriDate_byte, 2, 1, fp); + fwrite(&dir_oriBlock_byte, 2, 1, fp); + fwrite(&dir_oriSize, 4, 1, fp); + + fclose(fp); + return 0; +} + +struct tm get_time() { + struct timeval tv; + struct timezone tz; + struct tm *t; + + gettimeofday(&tv, &tz); + t = localtime(&tv.tv_sec); + return *t; +} +``` + + + +# 4. 文件 + +```c +int touch(char file_name[11], char disk_name[13], DISK disk_attr) { + FILE* contentFile = fopen("./data.txt", "r"); + char *content = (char*)malloc(2000 * sizeof(char)); + fread(content, 1, 2000, contentFile); + fclose(contentFile); + FILE* fp = fopen(disk_name, "rb+"); + int file_blockNum = strlen(content) / (disk_attr.BytsPerSec * disk_attr.SecPerClus) + 1; + unsigned short* index = (short*)malloc(sizeof(short) * file_blockNum); + for (int i = 0; i < file_blockNum; i++) { + index[i] = find_free_blocks(disk_attr, fp); + } + for (int i = 0; i < file_blockNum - 1; i++) { + fseek(fp, 0x200 + index[i] * 2, 0); + fwrite(&index[i + 1], 2, 1, fp); + } + fseek(fp, 0x200 + index[file_blockNum - 1] * 2, 0); + short last_index = 0xFFFF; + fwrite(&last_index, 2, 1, fp); + + int start_byte = disk_attr.current_dir; + int byte_line_num = 0; + while (byte_line_num < disk_attr.BytsPerSec) { + fseek(fp, start_byte + byte_line_num, 0); + int byte_line[4] = { 0 }; + fread(byte_line, 4, 3, fp); + if (!byte_line[0] && !byte_line[1] && !byte_line[2]) break; + byte_line_num += 0x20; + } + if (byte_line_num >= disk_attr.BytsPerSec) return -1; + fseek(fp, start_byte + byte_line_num, 0); + fwrite(file_name, 1, 11, fp); + short file_attr_byte = 0b00000000; + fwrite(&file_attr_byte, 1, 1, fp); + + short file_reserved_byte[6] = { 0 }; + fwrite(file_reserved_byte, 2, 5, fp); + + struct tm time = get_time(); + short file_time_byte = time.tm_hour * 2048 + time.tm_min * 32 + time.tm_sec / 2; + fwrite(&file_time_byte, 2, 1, fp); + short file_date_byte = (time.tm_year + 1900 - 1980) * 512 + time.tm_mon * 32 + time.tm_mday; + fwrite(&file_date_byte, 2, 1, fp); + fwrite(&index[0], 2, 1, fp); + unsigned int file_size = strlen(content); + fwrite(&file_size, 4, 1, fp); + + for (int i = 0; i < file_blockNum; i++) { + int new_block = 0x7800 + disk_attr.BytsPerSec * disk_attr.SecPerClus * index[i]; + fseek(fp, new_block, 0); + fwrite(content, + file_size > disk_attr.BytsPerSec * disk_attr.SecPerClus ? disk_attr.BytsPerSec * disk_attr.SecPerClus : file_size, + 1, + fp + ); + content += disk_attr.BytsPerSec * disk_attr.SecPerClus; + file_size -= disk_attr.BytsPerSec * disk_attr.SecPerClus; + } + fclose(fp); + return 0; +} +``` + + + + +