《Linux Device Driver》——并发和竞态

scull的缺陷

  if (!dptr->data[s_pos]) {
   
                dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
                if (!dptr->data[s_pos])
                        goto out;
        }

sucll代码中当有两个进程A、B正在独立尝试向同一个scull设备的相同偏移量写入数据,两个进程在同一时刻到达上述代码的第一个if处。执行接下来的kmalloc操作。当A先执行完,B后执行完时,B的结果会刷新A的结果,最终将A的结果覆盖。导致A分配的内存丢失。

这种情况就是竞态。竞态会导致对共享数据的非控制访问。


并发及其管理

内核代码是可抢占的,所以驱动程序代码可能在任何时候丢失对处理器的独占,而拥有处理器的进程可能正在调用我们的驱动程序代码。
设备中断时异步事件,也会导致代码的并发执行。
内核也会提供很多可延迟机制,workqueue、tasklet、time等,这些机制会使代码可在任何时刻执行,而不管当前进程在做什么。

竞态通常作为对资源的共享访问结果而产生。
在自己设计驱动程序时,应避免对资源的共享,尽量避免使用全局变量。

但是,资源的共享不可避免。硬件资源本质上就是共享的,而软件资源经常需要对其他执行线程可用。
全局变量并不是唯一的共享数据途径,只要我们将一个指针传递给内核的其他部分,一个新的共享就建立了。

资源共享的硬件规则:
1.在单个执行线程之外共享硬件或软件资源的时候,因为另一个线程可能对该资源的不一致观察,因此必须显式地管理对该资源的访问。
访问管理的常见技术称为”锁定“或者”互斥“——确保一次只有一个执行线程可操作共享资源。
2.当内核代码创建了一个可能和其他内核部分共享的对象时,该对象必须在还有其他组件引用自己时保持存在(并正确工作)。


信号量和互斥体

对上面提到的scull内存泄漏示例而言,我们需要对代码建立临界区:确保在任意给定时刻,代码只能被一个线程执行。

一个信号量(semaphore: 旗语,信号灯)本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为P和V。希望进入临界区的进程将在相关信号量上调用P;如果信号量的值大于零,则该值会减小一,而进程可以继续。相反,如果信号量的值为零(或更小),进程必须等待知道其他人释放该信号。对信号量的解锁通过调用V完成;该函数增加信号量的值,并在必要时唤醒等待的进程。
当信号量用于互斥时(即避免多个进程同时在一个临界区运行),信号量的值应初始化为1。这种信号量在任何给定时刻只能由单个进程或线程拥有。在这种使用模式下,一个信号量有时也称为一个“互斥体(mutex)”,它是互斥(mutual exclusion)的简称。
Linux内核中几乎所有的信号量均用于互斥。


Linux信号量的实现

使用信号量,内核代码必须包含<asm/semaphore.h> 。

信号量的创建和初始化:
1.创建信号量,通过sema_init完成:

void sema_init(struct semaphore *sem,int val);

val是赋给信号量一个初值。
2.信号量的声明和初始化:

	DECLARE_MUTEX(name);
   	DECLARE_MUTEX_LOCKED(name);

宏的作用是一个称为name的信号量变量被初始化为1(使用DECLARE_MUTEX)或者0(DECLARE_MUTEX_LOCKED)。
初始化为0 时,在允许任何线程访问之前,必须显示解锁该互斥体。

void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);

这两个宏表明在运行时被初始化(例如动态分配互斥体的状态下)。

P函数被称为down。
“down”减小信号量的值,也许会将调用者置于休眠状态,然后等待信号量变得可用,之后授予调用者对被保护资源的访问。
down的三个版本:

void down(struct semaphore*sem);/*不推荐使用,会建立不可杀进程*/
int down_interruptible(struct semaphore*sem);/*推荐使用,使用down_interruptible需要格外小心,若操作被中断,该函数会返回非零值,而调用这不会拥有该信号量。对down_interruptible的正确使用需要始终检查返回值,并做出相应的响应。*/
int down_trylock(struct semaphore*sem);/*带有“_trylock”的永不休眠,若信号量在调用是不可获得,会返回非零值。*/

V函数等价于up。

void up(struct semaphore*sem);/*任何拿到信号量的线程都必须通过一次(只有一次)对up的调用而释放该信号量。在出错时,要特别小心;若在拥有一个信号量时发生错误,必须在将错误状态返回前释放信号量。*/

在scull中使用信号量

正确使用锁的关键是明确指定需要保护的资源,并使用正确的锁来访问这些资源。

scull中scul.h确定了锁的范围:

struct scull_dev {
   
        struct scull_qset *data;  /* Pointer to first quantum set */
        int quantum;              /* the current quantum size */
        int qset;                 /* the current array size */
        unsigned long size;       /* amount of data stored here */
        unsigned int access_key;  /* used by sculluid and scullpriv */
        struct semaphore sem;     /* mutual exclusion semaphore     */
        struct cdev cdev;         /* Char device structure              */
};

信号量在使用前必须初始化。sucll在装载时进行初始化:

 /* Initialize each device. */
        for (i = 0; i < scull_nr_devs; i++) {
   
                scull_devices[i].
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值