[方案讨论]PCI相关结构锁粒度细化

注意,本文提到的“锁”均为读写锁RwLock

现状

目前pci相关的结构全部都是以“上大锁”的形式进行保存:
pci.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/pci/pci.rs
可见,这个大锁范围是很广的,它覆盖了所有的PCI结构,对于任何PCI结构的写都要求全部PCI设备上锁。

由于PCI结构上的是大锁,就导致pci结构在需要mut时,其整个结构都是可变的,这就意味着所有的属性都是可变的,即使它不应该变化:
pci.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/pci/pci.rs

上大锁就意味着计数引用(Arc)是不必要的,因为如果需要计数引用并要求可变,就必须提供内部可变性(这是通过Arc指针改变所指对象的唯一方法)。而大锁的原理就是粗暴地把整个结构上锁,它并不需要也不提供内部可变性,所以上大锁无法使用引用技术。

所以当前sysfs中对于pci总线子系统的做法是:在一开始的pci设备探测时复制一份pci结构以供用户查询。显然这种做法是十分粗糙的,因为用户通过sysfs查询到的数据仅仅是pci结构在启动时的快照,而且在sysfs中的任何操作都不会影响到内核的pci结构。

解决方案

我认为可以通过细化PCI相关结构的锁粒度(把大锁细化)来实现计数引用(Arc)的可用。

通过将PCI相关结构中需要mut的属性加上RwLock,即可实现PCI相关结构的内部可变性(不使用RefCell是因为RefCell不是多线程安全的),实现内部可变性后,仍然不能去掉 pci.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/pci/pci.rs的大锁,但是这里的Box已经可以改成Arc了


为什么大锁不去掉仍能使用Arc?

很简单,因为这里的大锁并不是直接锁在每个PCI设备结构上,这里的大锁是锁在存储PCI设备结构的链表上,也就是说,它现在有两个功能:

1.保护PCI设备结构这个临界区
2.保护链表本身

我们使用Arc实际上是移除了它的第一个功能,即我们不需要它再保护PCI设备结构了。如果它同时拥有这两个功能,就会导致任何一个PCI设备结构需要改变,整个链表就需要上写锁,任何人都无法读写任何PCI设备结构,即使它不是我们正在修改的结构

但是第二个功能是必须保留的,因为链表本身也是一个临界区,对它上锁是十分有必要的。

去掉第一个功能在代码上的体现就是,除非你需要在链表上添加新设备结构,否则你不再需要调用链表PCI_DEVICE_LINKEDLIST的write方法了,比如这些地方:
virtio.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/virtio/virtio.rs
mod.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/disk/ahci/mod.rs
e1000e.rs - OpenGrok cross reference for /DragonOS-0.1.9/kernel/src/driver/net/e1000e/e1000e.rs


不做内部可变性能否使用Arc?

不行,除非你完全不需要改变Arc所指的结构,即Arc指向的结构是个常量。

我曾经在使用Arc时碰到需要mut的情况通常很粗暴,直接copy:

let c: Arc<Person>=Arc::new(Person{...});
let ss: Person=*c.clone();
//对ss进行修改就当是对c修改了

但是这里任何对ss的修改,都不会反映在变量c中,更糟糕的是,这种操作要求Arc中的结构实现Copy trait,而Copy就意味着深拷贝,就意味着耗时。

如果你需要对c内部的Person进行修改,那么就必须要实现内部可变性:

let c:Arc<RefCell<Person>>=Arc::new(RefCell::new(Person{...}));
let ss = c.borrow_mut();
//这里对ss的修改会反映在c中

当然,你也可以细粒度地实现内部可变性:

struct Person{
    id:usize,
    stat:RefCell<u8>,
}
let c:Arc<Person>=Arc::new(Person{...});
let ss = c.stat.borrow_mut();
//这里对ss的修改会反映在s.stat中

细粒度的好处在于,person的id属性是不需要改变的,你没有办法通过Arc对其进行修改;如果是粗粒度的内部可变性,id属性就是可变的(即使你不想它被改变,它也可以被其他程序员改变)


对sysfs的pci总线子系统有好处吗?

当然,这可以一举解决一致性的问题。而且对于某些可能需要写入PCI结构的方法也是有益的。


但是内部可变性的实现工程量比较巨大,涉及代码较多,上述内容以及本解决方案是我的一家之言,由于不希望写到一半发现怎么路线全错了,所以发此贴以求指点:)

2 个赞

现有的方案有测试数据表明他因为竞争而导致速度慢吗?如果有,慢多少?

没有,目前的分析是静态代码分析,而且方案主要考虑的也不是性能问题。这里主要解决的问题是:PCI结构不能被sysfs持有的问题,并顺带解决了大锁带来的,可能存在的竞争问题。当然不修改也行,sysfs可以不持有PCI结构,它只需要在每次被访问时,遍历一遍PCI结构链表就行。

那可以改一下存储方式就行了,我觉得。
链表改为哈希表,也许能解决问题。

貌似改不了哈希,因为查找的键挺多的,有的代码按class查找,有的代码按vendorid和deviceid查找,感觉改哈希怪麻烦的