C Linux设备驱动程序访问控制
我正在为一些新的硬件实现一个设备驱动程序,希望一次只允许一个进程访问该设备。并发读/写操作会使硬件混乱到很可能需要硬复位的程度。我还有以下问题:C Linux设备驱动程序访问控制,c,linux,linux-kernel,linux-device-driver,C,Linux,Linux Kernel,Linux Device Driver,我正在为一些新的硬件实现一个设备驱动程序,希望一次只允许一个进程访问该设备。并发读/写操作会使硬件混乱到很可能需要硬复位的程度。我还有以下问题: 在示例代码中,open()调用使用锁,但close()不使用锁。这里是否仍然存在竞争条件,或者scull\u s\u count的减量是否保证是原子的?基本上,在这个例子中,我想知道当另一个进程结束并关闭设备时,如果一个进程正试图打开设备,会发生什么情况 我假设我不需要在我的read()和write()调用中检查打开标志的状态(我正在做一些类似于示例的
open()
调用使用锁,但close()
不使用锁。这里是否仍然存在竞争条件,或者scull\u s\u count
的减量是否保证是原子的?基本上,在这个例子中,我想知道当另一个进程结束并关闭设备时,如果一个进程正试图打开设备,会发生什么情况read()
和write()
调用中检查打开标志的状态(我正在做一些类似于示例的scull\u count
),因为进入这些调用的唯一方法是,如果用户空间应用程序已经通过成功调用open()
接收到fd
。这个假设正确吗原子机制进行了最粗略的搜索。下面是我现在拥有的一些伪代码:
int open(struct inode *inode, struct file *filp) {
spin_lock(&lock);
if (atomic_read(&open_flag)) {
spin_unlock(&lock);
return -EBUSY;
}
atomic_set(&open_flag, 1);
/* do other open() related stuff */
spin_unlock(&lock);
return 0;
}
int close(struct inode *inode, struct file *filp) {
int rc;
/* do close() stuff */
atomic_set(&open_flag, 0);
return rc;
}
open_标志
是一个atomic_t
,它是分配给kzalloc()
的较大结构的一部分。结果,它被初始化为零
因此,这里的代码表明锁的目的是防止多个进程/线程同时打开设备,而open\u标志
是一个atomic\u t
的事实防止了我在上面问题1中所关心的竞争条件。这个实现是否足够?此外,我仍在寻找问题2的答案
示例代码使用自旋锁,但互斥锁是否更合适?代码段相对较小,几乎没有争用,因此睡觉和醒来可能比旋转性能差。锁/互斥锁始终从用户上下文访问,因此您应该可以安全地睡眠。您所指的示例确实存在缺陷。减量绝对不能保证是原子的,而且几乎肯定不会
但实际上,我不认为有编译器/CPU组合会产生可能失败的代码。最糟糕的情况是,一个CPU内核可能完成关闭,然后另一个内核可能调用open并返回忙碌状态,因为它有一个过时的缓存标志值
Linux为此提供了原子*
函数,还提供了*\u位
原子位标志操作。请参阅内核文档中的core_api/atomic_ops.rst
正确且简单的his模式示例如下所示:
unsigned long mydriver_flags;
#define IN_USE_BIT 0
static int mydriver_open(struct inode *inode, struct file *filp)
{
if (test_and_set_bit(IN_USE_BIT, &mydriver_flags))
return -EBUSY;
/* continue with open */
return 0;
}
static int mydriver_close(struct inode *inode, struct file *filp)
{
/* do close stuff first */
smp_mb__before_atomic();
clear_bit(IN_USE_BIT, &mydriver_flags);
return 0;
}
对于每个设备,一个真正的驱动程序应该有一个设备状态结构,其中包含mydriver\u标志
。而不是对整个驱动程序使用一个全局变量,如示例所示
也就是说,你试图做的可能不是一个好主意。即使一次只有一个进程可以打开设备,进程的打开文件描述符也会在进程中的所有线程之间共享。多个线程可以同时对同一文件描述符进行read()
和write()
调用
如果进程打开了一个文件描述符并调用了fork()
,则该描述符将被继承到新进程中。这是一种多进程同时打开设备的方式,尽管存在上述“单一打开”限制
因此,在驱动程序的文件操作中,您仍然必须是线程安全的,因为用户仍然可以让多个线程/进程同时打开设备并进行调用。如果你已经做到了安全,为什么要阻止用户这么做呢?也许他们知道自己在做什么,并且会确保他们的多个司机的开场白会“轮流”打电话,而不会产生冲突
也考虑在开放调用中使用OAXEXL标志以使单选为可选的可能性。
< P>您认为问题是错的。如果硬件不能处理并发读/写,那么就由驱动程序来执行。驱动程序是可以访问硬件的单个进程。驱动程序允许以线程安全的方式访问自身。来自用户空间的读/写不应该直接进入硬件,它们应该由驱动程序处理,驱动程序处理硬件,但硬件需要。例如,write()处理程序可以将数据转储到队列中并设置一个标志,这样您的write_硬件无限循环就可以在可以的情况下将数据写入硬件。我对linux内核编程有点生疏,但同时使用原子和自旋锁对我来说似乎有点开销
希望一次只允许一个进程访问设备吗
如果这是您所需要的,那么scull实现工作正常,scull驱动程序的openimplementation中的自旋锁确保一次只有一个进程获得有效的文件句柄
我想我很确定(没有检查)skull示例在发布(close)中没有使用锁,因为只有打开它的进程才能关闭它,如果文件句柄无效,其他进程将无法进入发布代码
示例代码使用自旋锁,但互斥锁是否更合适
自旋锁实现速度更快,足以完成此任务
int scull_s_release(struct inode *inode, struct file *filp)
{
scull_s_count--; /* release the device */
/* from there until the function return is the only place where a race can occur
* so I wouldn't define the scull implementation "flawed" */
MOD_DEC_USE_COUNT;
return 0;
}
您和其他提供答案的人都正确地认为示例存在缺陷,而TrentP正确地认为,如果您使用原子位操作,如test\u和\u set\u bit()
(或者您可以使用atomic\u add\u,除非()
,等等),则根本不需要锁
然而,他的回答也不是完全正确的,因为它没有考虑到问题
static unsigned long mydriver_flags;
#define IN_USE_BIT 0
static int mydriver_open(struct inode *inode, struct file *filp)
{
if (test_and_set_bit(IN_USE_BIT, &mydriver_flags))
return -EBUSY;
/* continue with open */
return 0;
}
static int mydriver_close(struct inode *inode, struct file *filp)
{
/* do close stuff first */
smp_mb_before_atomic();
clear_bit(IN_USE_BIT, &mydriver_flags);
return 0;
}