操作系统如何判定资源属性并选择适配的并发处理方案?
OS从来不会在进程抢资源的时候,临场“识别”资源类型、“推导”适配的并发解决方案。内核本质是按预定义规则执行的状态机,所有资源的属性、对应的并发管控逻辑,在资源第一次被创建、注册到内核资源表的时候,就已经被创建方(内核自身、设备驱动、发起资源申请的用户态程序通过系统调用参数)显式写在资源控制块的标记位里了,OS运行时只需要按标记匹配逻辑执行即可。
可串行复用资源指的是同一时刻仅允许一个访问者操作、访问过程不会消耗资源、访问结束释放后可被下一个访问者重复使用的资源,比如打印机、内核全局链表、独占模式打开的文件、硬件临界寄存器等。
- 所谓OS“识别”这类资源,本质是读取资源控制块的预定义标记:资源创建时会写入
resource_flags字段,只要置位了串行复用标记,内核就会判定这类资源需要走互斥访问逻辑 - 这类资源的管控结构初始化逻辑是固定的:资源注册完成的瞬间,内核就会给它绑定一个初值为1的互斥信号量(mutex),挂在资源控制块的锁指针上
- 访问、释放的流程完全走预定义路径:
- 任何进程发起资源访问系统调用时,内核先查资源标记位,确认是串行复用资源后,自动对绑定的mutex执行P操作
- P操作成功的进程会被加入资源持有列表,允许进入临界区操作资源;P操作失败的进程会被直接挂到该信号量的等待队列,切为阻塞态
- 进程发起资源释放调用时,内核自动对mutex执行V操作,从等待队列中唤醒一个阻塞进程,移交资源访问权限
所有经典并发问题的对应解决方案,本质都是和资源的预定义属性绑定的,不存在OS临场适配的过程:
生产者-消费者场景
这类场景对应的核心资源是带容量上限的共享缓冲区,资源创建时会被打上“计数约束+队列串行修改”的组合标记,内核初始化管控结构时不会只配单个互斥锁,而是直接绑定三个同步原语:
- 初值等于缓冲区总长度的空位数信号量
empty - 初值为0的已填充数据块信号量
full - 初值为1的缓冲区操作互斥锁
mutex
后续进程访问时直接按预定义逻辑走:生产者先申请空位数、再拿操作锁,写完释放锁、再增加已填充计数;消费者先申请已填充数据块、再拿操作锁,读完释放锁、再增加空位数,整套流程是和资源类型强绑定的固定逻辑。
读者-写者场景
这类场景对应的资源是支持多读者并发读、写操作必须独占的共享资源,比如系统全局配置块、以读为主的共享内存段。资源创建时会打上读写共享标记,同时会由创建方指定队列调度策略(读优先/写优先/公平调度),内核初始化时会配套生成三个管控结构:
- 记录当前正在访问的读者数量的计数器
- 保护读者计数器修改操作的自旋锁
- 写者专用的独占访问锁
后续访问时,读者进来只需要拿计数锁、给计数器加1,第一个读者拿写锁、最后一个读者释放写锁,读的过程中其他读者可以直接进入;写者进来必须等所有读者、之前的写者都释放资源后,才能拿到独占锁访问,调度逻辑完全按创建时指定的策略执行。
哲学家就餐场景
这里要特别说明:OS默认不会自动适配这类场景的解决方案。因为哲学家就餐本质是多个独立的串行复用资源(每根筷子都是独立的可串行复用资源)被多个访问者交叉申请导致的死锁问题,OS无法感知上层申请多个资源的业务逻辑,自然也没法自动生成规避方案。
OS只提供基础的同步原语支持,死锁规避逻辑需要资源的创建方/使用方自己实现:要么给所有资源全局编号要求按固定顺序申请,要么用AND信号量机制要求一次性拿到所有需要的资源才允许进入临界区,要么限制同时申请资源的访问者总数。如果没有上层逻辑干预,OS最多只能在内核开启死锁检测的时候,识别到循环等待的死锁状态,通过杀进程的方式打破死锁,不会自动给这类多资源交叉申请的场景适配无死锁的调度方案。
内容的提问来源于stack exchange,提问作者thexbuttonisntworking

