20150518 Linux设备驱动中的并发控制
2015-05-18 Lover雪儿
总结一下并发控制的相关知识:
本文参考:华清远见《Linux 设备驱动开发详解》—第7章 Linux 设备驱动中的并发控制,更多详细内容请看原书
一、并发与竞态
并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。
在 Linux 内核中,主要的竞态发生于如下几种情况:
①对称多处理器(SMP)的多个 CPU
SMP 是一种紧耦合、共享存储的系统模型,如下图所示,它的特点是多个 CPU 使用共同的系统总线,因此可访问共同的外设和储存器。
②单
CPU
内进程与抢占它的进程
Linux
2.6 内核支持抢占调度,一个进程在内核执行的时候可能被另一高优先级进程打断,进程与抢占它的进程访问共享资源的情况类似于
SMP
的多个
CPU。
③中断(硬中断、软中断、Tasklet、底半部)与进程之间
中断可以打断正在执行的进程,如果中断处理程序访问进程正在访问的资源,则竞态也会发生。此外,中断也有可能被新的更高优先级的中断打断,因此,多个中断之间本身也可能引起并发而导致竞态
二、解决竞态的方法
解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。
访问共享资源的代码区域称为临界区(critical
sections),临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是
Linux
设备驱动中可采用的互斥途径,以下开始逐一讲解:
①中断屏蔽
在单
CPU
范围内避免竞态的一种简单方法是在进入临界区之前屏蔽系统的中断。
CPU
一般都具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核执行路径不被中断处理程序所抢占,防止某些竞态条件的发生。中断屏蔽将使得中断与进程之间的并发不再发生,从而避免了内核中的竞争了.
中断屏蔽的使用方法为: local_irq_disable() ... critical ... local_irq_enable() |
由于
Linux
系统的异步
I/O、进程调度等很多重要操作都依赖于中断,中断对于内核的运行非常重要,在屏蔽中断期间所有的中断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失甚至系统崩溃。这就要求在屏蔽了中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。
local_irq_disable()和
local_irq_enable()都只能禁止和使能本
CPU
内的中断,因此,并不能解决
SMP
多
CPU
引发的竞态。因此,单独使用中断屏蔽通常不是一种值得推荐的避免竞态的方法,它适宜与自旋锁联合使用。
与
local_irq_disable()不同的是,local_irq_save(flags)除了进行禁止中断的操作以外,还保存目前
CPU
的中断位信息,local_irq_restore(flags)进行的是local_irq_save(flags)相反的操作。
②原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
1.设置原子变量的值 void atomic_t //-------------------------------------------------------------------------- 2.获取原子变量的值 atomic_read(atomic_t //-------------------------------------------------------------------------- 3.原子变量加/减 void void //-------------------------------------------------------------------------- 4.原子变量自增/自减 void void //-------------------------------------------------------------------------- 5.操作并测试 int int int 上述操作对原子变量执行自增、自减和减操作后(注意没有加)测试其是否为 //-------------------------------------------------------------------------- 6.操作并返回 int int int int 上述操作对原子变量进行加/减和自增/自减操作,并返回新的值。 |
③位原子操作
1.设置位 void 上述操作设置 //-------------------------------------------------------------------------- 2.清除位 void 上述操作清除 //-------------------------------------------------------------------------- 3.改变位 void 上述操作对 --------------------------------------------------------------------------- 4.测试位 test_bit(nr, 上述操作返回 //-------------------------------------------------------------------------- 5.测试并操作位 int int int 上述 |
例.使用原子变量使设备只能被一个进程打开.
1 static atomic_t xxx_available = ATOMIC_INIT(1); /*定义原子变量*/ 2 static int xxx_open(struct inode *inode, struct file *filp){ 3 ... 4 if (!atomic_dec_and_test(&xxx_available)){ 5 atomic_inc(&xxx_available); 6 return - EBUSY; /*已经打开*/ 7 } 8 ... 9 return 0; /* 成功 */ 10 } 11 static int xxx_release(struct inode *inode, struct file *filp){ 12 atomic_inc(&xxx_available); /* 释放设备 */ 13 return 0; 14 }
④自旋锁
自旋锁(spin
lock)是一种对临界资源进行互斥手访问的典型手段.
为了获得一个自旋锁,在某
CPU
上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其他执行单元不可能访问这个内存变量。
如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行;如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置”操作,即进行所谓的“自旋”,通俗地说就是“在原地打转”。当自旋锁的持有者通过重置该变量释放这个自旋锁后,某个等待的“测试并设置”操作向其调用者报告锁已释放。
1.定义自旋锁 spinlock_t //-------------------------------------------------------------------------- 2.初始化自旋锁 spin_lock_init(lock) 该宏用于动态初始化自旋锁 //-------------------------------------------------------------------------- 3.获得自旋锁 spin_lock(lock) 该宏用于获得自旋锁 旋在那里,直到该自旋锁的保持者释放; spin_trylock(lock) 该宏尝试获得自旋锁 //-------------------------------------------------------------------------- 4.释放自旋锁 spin_unlock(lock) 该宏释放自旋锁一般这样被使用,如下所示: //定义一个自旋锁 spinlock_t spin_lock_init(&lock); spin_lock ...//临界区 spin_unlock //-------------------------------------------------------------------------- 自旋锁一般这样被使用,如下所示: //定义一个自旋锁 spinlock_t spin_lock_init(&lock); spin_lock ...//临界区 spin_unlock |
自旋锁主要针对
SMP
或单
CPU
但内核可抢占的情况,对于单
CPU
和内核不支持抢占的系统,自旋锁退化为空操作.
尽管用了自旋锁可以保证临界区不受别的
CPU
和本
CPU
内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。为了防止这种影响,就需要用到自旋锁的衍生。spin_lock()/spin_unlock()是自旋锁机制的基础,它们和关中断
local_irq_
disable()/开中断
local_irq_enable()、
关底半部local_bh_disable()/开底半部
local_bh_enable()、
关中断并保存状态字local_irq_save()/开中断并恢复状态
local_irq_restore()
结合就形成了整套自旋锁机制,关系如下所示:
spin_lock_irq() = spin_lock() + local_irq_disable() spin_unlock_irq() spin_lock_irqsave() spin_unlock_irqrestore() spin_lock_bh() spin_unlock_bh() |
驱动工程师应谨慎使用自旋锁,而且在使用中还要特别注意
自旋锁实际上是忙等锁,
自旋锁可能导致系统死锁
这两个问题.
int xxx_count = 0;/*定义文件打开次数计数*/ static int xxx_open(struct inode *inode, struct file *filp){ ... spinlock(&xxx_lock); if (xxx_count)/*已经打开*/ { spin_unlock(&xxx_lock); return - EBUSY; } xxx_count++;/*增加使用计数*/ spin_unlock(&xxx_lock); ... return 0; /* 成功 */ } static int xxx_release(struct inode *inode, struct file *filp){ ... spinlock(&xxx_lock); xxx_count--; /*减少使用计数*/ spin_unlock(&xxx_lock); return 0; }
⑤读写自旋锁
自旋锁不关心锁定的临界区究竟进行怎样的操作,不管是读还是写,它都一视同仁。即便多个执行单元同时读取临界资源也会被锁住。实际上,对共享资源并发访问时,多个执行单元同时读取它是不会有问题的,自旋锁的衍生锁读写自旋锁(rwlock)可允许读的并发。
1.定义和初始化读写自旋锁 rwlock_t rwlock_t rwlock_init(&my_rwlock); //-------------------------------------------------------------------------- 2.读锁定 void void void void //-------------------------------------------------------------------------- 3.读解锁 void void void void 在对共享资源进行读取之前,应该先调用读锁定函数,完成之后应调用读解锁函数。 read_lock_irqsave()、read_lock_irq()和 //-------------------------------------------------------------------------- 4.写锁定 void void void void int //-------------------------------------------------------------------------- 5.写解锁 void void void void write_lock_irqsave()、write_lock_irq()、write_lock_bh()分别是 write_unlock_irqrestore()、write_unlock_irq()、write_unlock_bh()的情况与此类似 |
在对共享资源进行读取之前,应该先调用写锁定函数,完成之后应调用写解锁函数。和
spin_trylock()一样,
write_trylock()也只是尝试获取读写自旋锁,不管成功失败,都会立即返回。
读写自旋锁一般这样被使用,如下所示: rwlock_t rwlock_init(&lock); //读时获取锁 read_lock(&lock); ... read_unlock(&lock); //写时获取锁 write_lock_irqsave(&lock, ... write_unlock_irqrestore(&lock, |
⑥顺序锁
顺序锁(seqlock)是对读写锁的一种优化,若使用顺序锁,读执行单元绝不会被写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。
但是,写执行单元与写执行单元之间仍然是互斥的,即如果有写执行单元在进行写操作,其他写执行单元必须自旋在那里,直到写执行单元释放了顺序锁。
如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据,以便确保得到的数据是完整的。这种锁在读写同时进行的概率比较小时,性能是非常好的,而且它允许读写同时进行,因而更大地提高了并发性。
顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致
Oops。
写操作
1.获得顺序锁 void int write_seqlock_irqsave(lock, write_seqlock_irq(lock) write_seqlock_bh(lock) 其中: write_seqlock_irqsave() write_seqlock_irq() write_seqlock_bh() 2.释放顺序锁 void write_sequnlock_irqrestore(lock, write_sequnlock_irq(lock) write_sequnlock_bh(lock) 其中: write_sequnlock_irqrestore() write_sequnlock_irq() write_sequnlock_bh() 写执行单元使用顺序锁的模式如下: write_seqlock(&seqlock_a); ...//写操作代码块 write_sequnlock(&seqlock_a); 因此,对写执行单元而言,它的使用与 |
读操作
1.读开始 unsigned read_seqbegin_irqsave(lock, 读执行单元在对被顺序锁 仅返回顺序锁 read_seqbegin_irqsave() 2.重读 int read_seqretry_irqrestore(lock, 读执行单元在访问完被顺序锁 访问期间是否有写操作。如果有写操作,读执行单元就需要重新进行读操作。其中: read_seqretry_irqrestore() 读执行单元使用顺序锁的模式如下: do seqnum //读操作代码块 ... } |
⑦读-拷贝-更新
RCU(Read-Copy
Update,读-拷贝-更新),它是基于其原理命名的。
对于被
RCU
保护的共享数据结构,读执行单元不需要获得任何锁就可以访问它,不使用原子指令,而且在除
Alpha
的所有架构上也不需要内存栅(Memory
Barrier),因此不会导致锁竞争、内存延迟以及流水线停滞。不需要锁也使得使用更容易,因为死锁问题就不需要考虑了。
使用
RCU
的写执行单元在访问它前需首先复制一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的
CPU
都退出对共享数据的操作的时候。读执行单元没有任何同步开销,而写执行单元的同步开销则取决于使用的写执行单元间的同步机制。
但是,RCU
不能替代读写锁,因为如果写比较多时,对读执行单元的性能提高不能弥补写执行单元导致的损失。因为使用
RCU
时,写执行单元之间的同步开销会比较大,它需要延迟数据结构的释放,复制被修改的数据结构,它也必须使用某种锁机制同步并行的其他写执行单元的修改操作。
1.读锁定 rcu_read_lock() rcu_read_lock_bh() 2.读解锁 rcu_read_unlock() rcu_read_unlock_bh() 使用 rcu_read_lock() ...//读临界区 rcu_read_unlock() 其中 #define #define 其变种 #define #define 3.同步 synchronize_rcu() 该函数由 synchronize_kernel() 内核代码使用该函数来等待所有 4.挂接回调 void 函数 void call_ruc_bh()函数的功能几乎与 |
每个
CPU
维护两个数据结构
rcu_data
和
rcu_bh_data,它们用于保存回调函数,函数
call_rcu()把回调函数注册到
rcu_data,而
call_rcu_bh()则把回调函数注册到rcu_bh_data,在每一个数据结构上,回调函数被组成一个链表,先注册的排在前头,后注册的排在末尾。
使用
RCU
时,读执行单元必须提供一个信号给写执行单元以便写执行单元能够确定数据可以被安全地释放或修改的时机。有一个专门的垃圾收集器来探测读执行单元的信号,一旦所有的读执行单元都已经发送信号告知它们都不在使用被
RCU
保护的数据结构,垃圾收集器就调用回调函数完成最后的数据释放或修改操作。
RCU
还增加了链表操作函数的
RCU
版本
static inline void list_add_rcu(struct list_head *new, struct list_head *head); 该函数把链表元素 static struct 该函数类似于 static 该函数从 static list_head 该函数是 list_for_each_rcu(pos, 该宏用于遍历由 list_for_each_safe_rcu(pos, 该宏类似于 list_for_each_entry_rcu(pos, 该宏类似于 static 它从由 static struct 该函数用于把链表元素 hlist_for_each_rcu(pos, 该宏用于遍历由 hlist_for_each_entry_rcu(tpos, 类似于 |
目前,RCU
的使用在内核中已经非常普遍,内核中大量原先使用读写锁的代码被RCU
替换,下面的表单左右两列平行地分别给出使用读写锁和
RCU
实现链表元素读、删除、添加和修改的代码。
⑧信号量的使用
信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
1.定义信号量 下列代码定义名称为 struct 2.初始化信号量 void 该函数初始化信号量,并设置信号量 void 该函数用于初始化一个用于互斥的信号量,它把信号量 sema_init void 该函数也用于初始化一个信号量,但它把信号量 sema_init 此外,下面两个宏是定义并初始化信号量的“快捷方式”。 DECLARE_MUTEX(name) DECLARE_MUTEX_LOCKED(name) 前者定义一个名为 3.获得信号量 void 该函数用于获得信号量 int 该函数功能与 int 该函数尝试获得信号量 if return } 4.释放信号量 void 该函数释放信号量 信号量一般这样被使用,如下所示: //定义信号量 DECLARE_MUTEX(mount_sem); down(&mount_sem);//获取信号量,保护临界区 ... critical ... up(&mount_sem);//释放信号量 |
⑨完成量用于同步
Linux
系统提供了一种比信号量更好的同步机制,即完成量:它用于一个执行单元等待另一个执行单元执行完某事.
1.定义完成量 下列代码定义名为 struct 2.初始化 下列代码初始化 init_completion(&my_completion); 对 DECLARE_COMPLETION(my_completion); 3.等待完成量 下列函数用于等待一个 void 4.唤醒完成量 下面两个函数用于唤醒完成量。 void void 前者只唤醒一个等待的执行单元,后者释放所有等待同一完成量的执行单元。 |
⑩信号量vs自旋锁
信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU
将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。
当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是
CPU
得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。
使用的三项原则: 1.当锁不能被获取时,使用信号量的开销是进程上下文切换时间 2.信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。 3.信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 |
⑾读写信号量
读写信号量可能引起进程阻塞,但它可允许
N
个读执行单元同时访问共享资源,而最多只能有一个写执行单元。因此,读写信号量是一种相对放宽条件的粒度稍大于信号量的互斥机制。
1.定义和初始化读写信号量 struct void 2.读信号量获取 void int 3.读信号量释放 void 4.写信号量获取 void int 5.写信号量释放 void 读写信号量一般这样被使用,如下所示: rw_semaphore init_rwsem(&rw_sem); //读时获取信号量 down_read(&rw_sem); ... up_read(&rw_sem); //写时获取信号量 down_write(&rw_sem); ... up_write(&rw_sem); |
⑿互斥体
尽管信号量已经可以实现互斥的功能,而且包含
DECLARE_MUTEX()
、init_MUTEX
()等定义信号量的宏或函数,
从名字上看就体现出了互斥体的概念,
但是mutex
在
Linux
内核中还是真实地存在的。
struct mutex my_mutex; mutex_init(&my_mutex); 下面的两个函数用于获取互斥体。 void int int 下列函数用于释放互斥体。 void 使用方法: struct mutex_init(&my_mutex); mutex_lock(&my_mutex); ...//临界资源 mutex_unlock(&my_mutex); |
附驱动程序示例:
1 #include <linux/module.h> 2 #include <linux/types.h> 3 #include <linux/fs.h> 4 #include <linux/kernel.h> 5 #include <linux/device.h> 6 #include <linux/errno.h> 7 #include <linux/mm.h> 8 #include <linux/sched.h> 9 #include <linux/init.h> 10 #include <linux/cdev.h> 11 #include <asm/io.h> 12 #include <asm/system.h> 13 #include <asm/uaccess.h> 14 #include <linux/sem.h> 15 16 #define GLOBALMEM_SIZE 0x1000 /*全局内存大小:4KB*/ 17 #define MEM_CLEAR 0x1 /*清零全局内存*/ 18 #define GLOBALMEM_MAJOR 0 /*预设的 globalmem 的主设备号*/ 19 20 21 static int globalmem_major = GLOBALMEM_MAJOR; 22 /*globalmem 设备结构体*/ 23 struct globalmem_dev 24 { 25 struct cdev cdev; /*cdev 结构体*/ 26 unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/ 27 struct semaphore sem; /* 1. 并发控制用的信号量 */ 28 }; 29 30 struct globalmem_dev *globalmem_devp; /*设备结构体实例*/ 31 32 //自动添加设备节点 33 static struct class *cls = NULL; 34 35 36 /*文件打开函数*/ 37 static int globalmem_open(struct inode *inode, struct file *filp){ 38 /*将设备结构体指针赋值给文件私有数据指针*/ 39 filp->private_data = globalmem_devp; 40 return 0; 41 } 42 //释放函数 43 static int globalmem_release(struct inode *inode, struct file *filp){ 44 return 0; 45 } 46 //文件读函数 47 static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos) 48 { 49 unsigned long p = *ppos; 50 int ret = 0; 51 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 52 /*分析和获取有效的读长度*/ 53 if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界 54 return count ? - ENXIO: 0; 55 if (count > GLOBALMEM_SIZE - p)//要读的字节数太大 56 count = GLOBALMEM_SIZE - p; 57 58 if(down_interruptible(&dev->sem)) //3.获取信号量 59 { 60 return -ERESTARTSYS; 61 } 62 63 /*内核空间→用户空间*/ 64 if (copy_to_user(buf, (void*)(dev->mem + p), count)) 65 { 66 ret = - EFAULT; 67 }else{ 68 *ppos += count; 69 ret = count; 70 printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p); 71 } 72 73 up(&dev->sem); //4.释放信号量 74 75 return ret; 76 } 77 //文件写函数 78 static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos) 79 { 80 unsigned long p = *ppos; 81 int ret = 0; 82 struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/ 83 /*分析和获取有效的写长度*/ 84 if (p >= GLOBALMEM_SIZE) //要写的偏移位置越界 85 return count ? - ENXIO: 0; 86 if (count > GLOBALMEM_SIZE - p) //要写的字节数太多 87 count = GLOBALMEM_SIZE - p; 88 89 if(down_interruptible(&dev->sem)) //4.获取信号量 90 { 91 return -ERESTARTSYS; 92 } 93 94 /*用户空间→内核空间*/ 95 if (copy_from_user(dev->mem + p, buf, count)){ 96 ret = - EFAULT; 97 }else{ 98 *ppos += count; 99 ret = count; 100 printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p); 101 } 102 up(&dev->sem); //释放信号量 103 return ret; 104 } 105 //文件定位函数 106 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) 107 { 108 loff_t ret; 109 switch (orig){ 110 case 0: /*从文件开头开始偏移*/ 111 if (offset < 0){ 112 ret = - EINVAL; 113 break; 114 } 115 if ((unsigned int)offset > GLOBALMEM_SIZE) //偏移越界 116 { 117 ret = - EINVAL; 118 break; 119 } 120 filp->f_pos = (unsigned int)offset; 121 ret = filp->f_pos; 122 break; 123 124 case 1: /*从当前位置开始偏移*/ 125 if ((filp->f_pos + offset) > GLOBALMEM_SIZE) //偏移越界 126 { 127 ret = - EINVAL; 128 break; 129 } 130 if ((filp->f_pos + offset) < 0) 131 { 132 ret = - EINVAL; 133 break; 134 } 135 filp->f_pos += offset; 136 ret = filp->f_pos; 137 break; 138 default: ret = - EINVAL; break; 139 } 140 return ret; 141 } 142 // 143 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg) 144 { 145 struct globalmem_dev *dev = filp->private_data; 146 switch (cmd){ 147 case MEM_CLEAR: //清除全局内存 148 149 if(down_interruptible(&dev->sem)) //3.获取信号量 150 { 151 return -ERESTARTSYS; 152 } 153 memset(dev->mem, 0, GLOBALMEM_SIZE); 154 up(&dev->sem); //释放信号量 155 printk(KERN_INFO "globalmem is set to zero\n"); 156 break; 157 default: 158 return - EINVAL; //其他不支持的命令 159 break; 160 } 161 return 0; 162 } 163 //定义file_operatetiont 164 static const struct file_operations globalmem_fops = 165 { 166 .owner = THIS_MODULE, 167 .llseek = globalmem_llseek, 168 .read = globalmem_read, 169 .write = globalmem_write, 170 .ioctl = globalmem_ioctl, 171 .open = globalmem_open, 172 .release = globalmem_release, 173 }; 174 175 /*globalmem 设备驱动模块加载函数*/ 176 static int globalmem_init(void){ 177 int result,err; 178 dev_t devno = MKDEV(globalmem_major, 0); 179 /* 申请字符设备驱动区域*/ 180 if (globalmem_major){ 181 result = register_chrdev_region(devno, 1, "globalmem"); 182 }else{ 183 /* 动态获得主设备号 */ 184 result = alloc_chrdev_region(&devno, 0, 1, "globalmem"); 185 globalmem_major = MAJOR(devno); 186 } 187 if (result < 0) 188 return result; 189 printk("request major %d,minor %d\n",globalmem_major,MINOR(devno)); 190 191 /*动态申请设备结构体的内存*/ 192 globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL); 193 if(!globalmem_devp){ /* 申请失败 */ 194 result = -ENOMEM; 195 goto fail_malloc; 196 } 197 memset(globalmem_devp, 0, sizeof(struct globalmem_dev)); 198 199 /*初始化设备结构体*/ 200 cdev_init(&globalmem_devp->cdev, &globalmem_fops); 201 globalmem_devp->cdev.owner = THIS_MODULE; 202 globalmem_devp->cdev.ops = &globalmem_fops; 203 /* 注册cdev */ 204 err = cdev_add(&globalmem_devp->cdev, devno, 1); 205 if (err) 206 printk(KERN_NOTICE "Error %d adding globalmem", err); 207 208 //自动创建设备节点 209 cls = class_create(THIS_MODULE,"globalmem"); 210 device_create(cls, NULL, devno,NULL,"globalmem"); 211 212 init_MUTEX(&globalmem_devp->sem); //2.初始化信号量 213 214 return 0; 215 fail_malloc: unregister_chrdev_region(devno,1); 216 return result; 217 } 218 /*globalmem 设备驱动模块卸载函数*/ 219 static void globalmem_exit(void) 220 { 221 dev_t devno = MKDEV(globalmem_major, 0); 222 223 device_destroy(cls,devno); 224 class_destroy(cls); 225 cdev_del(&globalmem_devp->cdev); /*删除 cdev 结构*/ 226 kfree(globalmem_devp); /* 释放设备结构体内存 */ 227 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/*注销设备*/ 228 } 229 230 module_init(globalmem_init); 231 module_exit(globalmem_exit); 232 MODULE_LICENSE("GPL");
mutex_globalmem.c
测试实例:
[email protected] /mnt/nfs/module/48_mutex_globalmem# echo "hello,LoverXueEr" > /dev/globalmem written [email protected] hello,LoverXueEr |