Skip to content

Commit 68c58eb

Browse files
committed
update ch3:exercise ch8:exercise
1 parent b2b0763 commit 68c58eb

File tree

2 files changed

+144
-12
lines changed

2 files changed

+144
-12
lines changed

source/chapter3/5exercise.rst

+76-1
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,91 @@
4848
实践作业
4949
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5050

51+
获取任务信息
52+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53+
54+
ch3 中,我们的系统已经能够支持多个任务分时轮流运行,我们希望引入一个新的系统调用 ``sys_task_info`` 以获取任务的信息,定义如下:
55+
56+
.. code-block:: rust
57+
58+
fn sys_task_info(id: usize, ts: *mut TaskInfo) -> isize
59+
60+
- syscall ID: 410
61+
- 根据任务 ID 查询任务信息,任务信息包括任务 ID、任务控制块相关信息(任务状态)、任务使用的系统调用及调用次数、任务总运行时长。
62+
63+
.. code-block:: rust
64+
65+
struct TaskInfo {
66+
id: usize,
67+
status: TaskStatus,
68+
call: [SyscallInfo; MAX_SYSCALL_NUM],
69+
time: usize
70+
}
71+
72+
- 系统调用信息采用数组形式对每个系统调用的次数进行统计,相关结构定义如下:
73+
74+
.. code-block:: rust
75+
76+
struct SyscallInfo {
77+
id: usize,
78+
times: usize
79+
}
80+
81+
- 参数:
82+
- id: 待查询任务id
83+
- ts: 待查询任务信息
84+
- 返回值:执行成功返回0,错误返回-1
85+
- 说明:
86+
- 相关结构已在框架中给出,只需添加逻辑实现功能需求即可。
87+
- 提示:
88+
- 大胆修改已有框架!除了配置文件,你几乎可以随意修改已有框架的内容。
89+
- 程序运行时间可以通过调用 ``get_time()`` 获取。
90+
- 系统调用次数可以考虑在进入内核态系统调用异常处理函数之后,进入具体系统调用函数之前维护。
91+
- 阅读 TaskManager 的实现,思考如何维护内核控制块信息(可以在控制块可变部分加入其他需要的信息)
92+
93+
打印调用堆栈(选做)
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95+
96+
我们在调试程序时,除了正在执行的函数外,往往还需要知道当前的调用堆栈。这样的功能通常由调试器、运行环境、 IDE 或操作系统等提供,但现在我们只能靠自己了。最基本的实现只需打印出调用链上的函数地址,更丰富的功能包括打印出函数名、函数定义、传递的参数等等。
97+
98+
本实验我们不提供新的测例,仅提供参考实现,各位同学可以通过对照 GDB 、参考实现或自行构造调用链等方式检验自己的实现是否正确。
99+
100+
.. hint:: 可以参考《编译原理》课程中关于函数调用栈帧的内容。
51101

52102
实验要求
53103
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54104

105+
- 完成分支: ch3-lab
106+
107+
- 实验目录要求
108+
109+
.. code-block::
110+
111+
├── os(内核实现)
112+
│   ├── Cargo.toml(配置文件)
113+
│   └── src(所有内核的源代码放在 os/src 目录下)
114+
│   ├── main.rs(内核主函数)
115+
│   └── ...
116+
├── reports (不是 report)
117+
│   ├── lab3.md/pdf
118+
│   └── ...
119+
├── ...
120+
121+
122+
- 通过所有已有的测例:
123+
124+
CI 使用的测例与本地相同,测试中,user 文件夹及其它与构建相关的文件将被替换,请不要试图依靠硬编码通过测试。
125+
126+
.. note::
127+
128+
你的实现只需且必须通过测例,建议读者感到困惑时先检查测例。
129+
55130
实验约定
56131
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
57132

58133
问答作业
59134
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60-
135+
61136

62137

63138
实验练习的提交报告要求

source/chapter8/6exercise.rst

+68-11
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@
1111

1212
信号量是一种特殊的变量,可用于线程同步。它只取自然数值,并且只支持两种操作:P(SV): 如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。V(SV): 如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。其系统调用为:
1313

14-
- sem_waitsem_t *sem:以原子操作的方式将信号量减1,如果信号量值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。
15-
- sem_postsem_t *sem):以原子操作将信号量值+1。当信号量大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。
14+
- `sem_wait(sem_t *sem)`:以原子操作的方式将信号量减1,如果信号量值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。
15+
- `sem_post(sem_t *sem)`:以原子操作将信号量值+1。当信号量大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。
1616

1717
互斥量:互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。其主要的系统调用如下:
1818

19-
- pthread_mutex_init: 初始化互斥锁
20-
- pthread_mutex_destroy: 销毁互斥锁
19+
- `pthread_mutex_init`: 初始化互斥锁
20+
- `pthread_mutex_destroy`: 销毁互斥锁
2121
- pthread_mutex_lock: 以原子操作的方式给一个互斥锁加锁,如果目标互斥锁已经被上锁,pthread_mutex_lock调用将阻塞,直到该互斥锁的占有者将其解锁。
22-
- pthread_mutex_unlock: 以一个原子操作的方式给一个互斥锁解锁。
22+
- `pthread_mutex_unlock`: 以一个原子操作的方式给一个互斥锁解锁。
2323

2424

2525
条件变量:条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。即,当某个共享变量等于某个值时,调用 signal/broadcast。此时操作共享变量时需要加锁。其主要的系统调用如下:
2626

27-
- pthread_cond_init: 初始化条件变量
28-
- pthread_cond_destroy: 销毁条件变量
29-
- pthread_cond_signal: 唤醒一个等待目标条件变量的线程。哪个线程被唤醒取决于调度策略和优先级。
30-
- pthread_cond_wait: 等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。该函数中在进入wait状态前首先进行解锁,然后接收到信号后会再加锁,保证该线程对共享资源正确访问。
27+
- `pthread_cond_init`: 初始化条件变量
28+
- `pthread_cond_destroy`: 销毁条件变量
29+
- `pthread_cond_signal`: 唤醒一个等待目标条件变量的线程。哪个线程被唤醒取决于调度策略和优先级。
30+
- `pthread_cond_wait`: 等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。该函数中在进入wait状态前首先进行解锁,然后接收到信号后会再加锁,保证该线程对共享资源正确访问。
3131

3232
1. `**` 在Linux环境下,请用信号量实现哲学家就餐的多线程应用程序。
3333
2. `**` 在Linux环境下,请用互斥锁和条件变量实现哲学家就餐的多线程应用程序。
@@ -118,18 +118,75 @@
118118
编程作业
119119
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
120120

121-
并发
121+
银行家算法——分数更新
122122
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
123123

124-
这一章我们实现了并发机制...
124+
.. note::
125+
126+
本实验为用户态实验,请在 Linux 环境下完成。
127+
128+
背景:在智能体大赛平台 `Saiblo <https://www.saiblo.net>`_ 网站上每打完一场双人天梯比赛后需要用 ELO 算法更新双方比分。由于 Saiblo 的评测机并发性很高,且 ELO 算法中的分值变动与双方变动前的分数有关,因此更新比分前时必须先为两位选手加锁。
129+
130+
作业:请模拟一下上述分数更新过程,简便起见我们简化为有 p 位选手参赛(编号 [0, p) 或 [1, p] ),初始分值为 1000 分,有 m 个评测机线程(生产者)给出随机的评测结果(两位不同选手的编号以及胜负结果,结果可能为平局),有 n 个 worker 线程(消费者)获取结果队列并更新数据库(全局变量等共享数据)记录的分数。m 个评测机各自模拟 k 场对局结果后结束线程,全部对局比分更新完成后主线程打印每位选手最终成绩以及所有选手分数之和。
131+
132+
上述参数 p、m、n、k 均为可配置参数(命令行传参或程序启动时从stdin输入)。
133+
134+
简便起见不使用 ELO 算法,简化更新规则为:若不为平局,当 胜者分数 >= 败者分数 时胜者 +20,败者 -20,否则胜者 +30,败者 -30;若为平局,分高者 -10,分低者+10(若本就同分保持则不变)。
135+
136+
消费者核心部分可参考如下伪码:
137+
获取选手A的锁
138+
获取选手B的锁
139+
更新A、B分数
140+
睡眠 1ms(模拟数据库更新延时)
141+
释放选手B的锁
142+
释放选手A的锁
143+
144+
tips:
145+
- 由于 ELO 以及本题中给出的简化更新算法均为零和算法,因此出现冲突后可以从所有选手分数之和明显看出来,正确处理时它应该永远为 1000p
146+
- 将一个 worker 线程看作哲学家,将 worker 正在处理的一场对局的两位选手看作两根筷子,则得到了经典的哲学家就餐问题
147+
148+
实现 eventfd
149+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
150+
151+
在 Linux 中有一种用于事件通知的文件描述符,称为 eventfd 。其核心是一个 64 位无符号整数的计数器,在非信号量模式下,若计数器值不为零,则 `read` 函数会从中读出计数值并将其清零,否则读取失败; `write` 函数将缓冲区中的数值加入到计数器中。在信号量模式下,若计数器值非零,则 `read` 操作将计数值减一,并返回 1 ; `write` 将计数值加一。我们将实现一个新的系统调用: `sys_eventfd2` 。
152+
153+
**eventfd**:
154+
155+
* syscall ID: 290
156+
* 功能:创建一个 eventfd, `eventfd 标准接口 <https://linux.die.net/man/2/eventfd>`_ 。
157+
* C 接口: ``int eventfd(unsigned int initval, int flags)``
158+
* Rust 接口: ``fn eventfd(initval: u32, flags: i32) -> i32``
159+
* 参数:
160+
* initval: 计数器的初值。
161+
* flags: 可以设置为 0 或以下两个 flag 的任意组合(按位或):
162+
* EFD_SEMAPHORE (1) :设置该 flag 时,将以信号量模式创建 eventfd 。
163+
* EFD_NONBLOCK (2048) :若设置该 flag ,对 eventfd 读写失败时会返回 -2 ,否则将阻塞等待直至读或写操作可执行为止。
164+
* 说明:
165+
* 通过 `write` 写入 eventfd 时,缓冲区大小必须为 8 字节。
166+
* 进程 `fork` 时,子进程会继承父进程创建的 eventfd ,且指向同一个计数器。
167+
* 返回值:如果出现了错误则返回 -1,否则返回创建成功的 eventfd 编号。
168+
* 可能的错误
169+
* flag 不合法。
170+
* 创建的文件描述符数量超过进程限制
171+
172+
.. note::
173+
还有一个 `sys_eventfd` 系统调用(调用号 284),与 `sys_eventfd2` 的区别在于前者不支持传入 flags 。
174+
175+
Linux 中的原生异步 IO 接口 libaio 就使用了 eventfd 作为内核完成 IO 操作之后通知应用程序的机制。
176+
177+
125178

126179
实验要求
127180
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128181

182+
- 完成分支: ch8-lab
183+
- 实验目录要求不变。
184+
- 通过所有测例。
129185

130186
问答作业
131187
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132188

189+
133190

134191
实验练习的提交报告要求
135192
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

0 commit comments

Comments
 (0)