chapter8练习

编程作业

银行家算法——分数更新

注解

本实验为用户态实验,请在 Linux 环境下完成。

背景:在智能体大赛平台 Saiblo 网站上每打完一场双人天梯比赛后需要用 ELO 算法更新双方比分。由于 Saiblo 的评测机并发性很高,且 ELO 算法中的分值变动与双方变动前的分数有关,因此更新比分前时必须先为两位选手加锁。

作业:请模拟一下上述分数更新过程,简便起见我们简化为有 p 位选手参赛(编号 [0, p) 或 [1, p] ),初始分值为 1000 分,有 m 个评测机线程(生产者)给出随机的评测结果(两位不同选手的编号以及胜负结果,结果可能为平局),有 n 个 worker 线程(消费者)获取结果队列并更新数据库(全局变量等共享数据)记录的分数。m 个评测机各自模拟 k 场对局结果后结束线程,全部对局比分更新完成后主线程打印每位选手最终成绩以及所有选手分数之和。

上述参数 p、m、n、k 均为可配置参数(命令行传参或程序启动时从stdin输入)。

简便起见不使用 ELO 算法,简化更新规则为:若不为平局,当 胜者分数 >= 败者分数 时胜者 +20,败者 -20,否则胜者 +30,败者 -30;若为平局,分高者 -10,分低者+10(若本就同分保持则不变)。

消费者核心部分可参考如下伪码:

获取选手A的锁 获取选手B的锁 更新A、B分数 睡眠 1ms(模拟数据库更新延时) 释放选手B的锁 释放选手A的锁

tips:
  • 由于 ELO 以及本题中给出的简化更新算法均为零和算法,因此出现冲突后可以从所有选手分数之和明显看出来,正确处理时它应该永远为 1000p

  • 将一个 worker 线程看作哲学家,将 worker 正在处理的一场对局的两位选手看作两根筷子,则得到了经典的哲学家就餐问题

实现 eventfd

在 Linux 中有一种用于事件通知的文件描述符,称为 eventfd 。其核心是一个 64 位无符号整数的计数器,在非信号量模式下,若计数器值不为零,则 read 函数会从中读出计数值并将其清零,否则读取失败; write 函数将缓冲区中的数值加入到计数器中。在信号量模式下,若计数器值非零,则 read 操作将计数值减一,并返回 1 ; write 将计数值加一。我们将实现一个新的系统调用: sys_eventfd2

eventfd

  • syscall ID: 290

  • 功能:创建一个 eventfd, eventfd 标准接口

  • C 接口: int eventfd(unsigned int initval, int flags)

  • Rust 接口: fn eventfd(initval: u32, flags: i32) -> i32

  • 参数:
    • initval: 计数器的初值。

    • flags: 可以设置为 0 或以下两个 flag 的任意组合(按位或):
      • EFD_SEMAPHORE (1) :设置该 flag 时,将以信号量模式创建 eventfd 。

      • EFD_NONBLOCK (2048) :若设置该 flag ,对 eventfd 读写失败时会返回 -2 ,否则将阻塞等待直至读或写操作可执行为止。

  • 说明:
    • 通过 write 写入 eventfd 时,缓冲区大小必须为 8 字节。

    • 进程 fork 时,子进程会继承父进程创建的 eventfd ,且指向同一个计数器。

  • 返回值:如果出现了错误则返回 -1,否则返回创建成功的 eventfd 编号。

  • 可能的错误
    • flag 不合法。

    • 创建的文件描述符数量超过进程限制

注解

还有一个 sys_eventfd 系统调用(调用号 284),与 sys_eventfd2 的区别在于前者不支持传入 flags 。

Linux 中的原生异步 IO 接口 libaio 就使用了 eventfd 作为内核完成 IO 操作之后通知应用程序的机制。

实验要求

  • 完成分支: ch8。

  • 实验目录要求不变。

  • 通过所有测例。

你的内核必须前向兼容,能通过前一章的所有测例。

报告要求

  • 简单总结你实现的功能(200字以内,不要贴代码)。

  • 完成问答题。

  • (optional) 你对本次实验设计及难度/工作量的看法,以及有哪些需要改进的地方,欢迎畅所欲言。