注意,本文提到的“锁”均为读写锁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结构的方法也是有益的。
但是内部可变性的实现工程量比较巨大,涉及代码较多,上述内容以及本解决方案是我的一家之言,由于不希望写到一半发现怎么路线全错了,所以发此贴以求指点:)