Linux内核中并发和竞态的解决方法
竞态产生的原因
当多个进程访问同一个驱动的临界资源的时候,竞态就会产生了。
( 临界资源:当多个进程都能调用写write的时候一定会产生竞态的,如果多个进程同时调用读read的时候不会产生竞态。)
之前在线程的时候也讲到过竞态,当多个线程访问同一个全局变量的时候,就会产生竞态。(张三先生和李四取钱的例子)。解决办法:1.互斥—线程互斥锁 2.同步–条件变量 或 无名信号量
竞态产生的根本原因
产生竞态的原因:不同进程,通过同一个驱动, 访问同一个硬件
我们要让一个驱动只能被一个进程控制
对于单核处理器来说,如果内核支持抢占就会产生竞态。(如果不支持抢占就顺序执行,不会有竞态)
对于多核处理器(smp)来说,核与核之间本身就是并行执行,也会产生竞态。
中断和进程间也会产生竞态。(在中断中,如果和进程访问的是同一个资源,也会产生竞态)
在ARM架构下,中断和中断间会产生竞态。 这句话是不对的。因为我们现在arm架构下,中断不支持嵌套。
解决竞态的方法
中断屏蔽(了解) pass
自旋锁(重点):如果涉及到中断了,中断上下文,只能使用自旋锁。
信号量(重点):
互斥体(会用):
原子操作(会用):不需要阻塞休眠,也不需要防自旋的话选原子操作
总结:
如果是进程上下文,自旋锁、信号量、互斥体都可以,这时候要看临界区,临界区大则排除掉自旋锁。在不确定临界区大小的时候直接用互斥体。用信号量也行,但是如果临界区很小的话,效率会低。
面试题问题:
解决竞态的方法有哪些?
自旋锁和信号量有哪些区别?
自旋锁的特点?
中断屏蔽
中断屏蔽:只针对单核有效,中断屏蔽就是临时将中断关闭掉。
中断屏蔽保护的临界区比较小,在临界区内不能使用耗时,延时,甚至休眠的操作。否则中断屏蔽的时间比较长就导致用户数据的丢失或者内核的崩溃。
local_irq_disable(); //屏蔽中断
//临界区
local_irq_enable(); //开启中断
自旋锁
什么是自旋锁
自旋锁:当一个进程获取到自旋锁之后,如果此时另外一个进程也想获取这把锁,后一个进程就处于自旋状态,自旋锁又叫忙等锁。
自旋锁的特点
1.自旋锁本身就是针对 多核 设计的。
2.自旋状态是需要消耗cpu资源的(但效率不一定低,处于临界的话效率高)。
3.自旋锁保护的 临界区比较小 ,不能够在自旋锁内核使用延时,耗时,休眠等操作。
在自旋锁内核不能够调用copy_to_user、copy_from_user等类似的函数。
( 加锁的目的是在执行代码临界区的时候不能被打断,copy_to_user、copy_from_user这两个函数如果执行的时间比较长,在执行代码的时候有可能被打断,执行其他的任务。 也不能调用 类似sleep的函数 sleep函数会让进程休眠 cpu可能会执行其他进程 )
4.自旋锁可以工作在中断上下文。( 自旋锁可以在中断中使用 )
5.自旋锁在上锁前会关闭抢占。
6.自旋锁可能会产生死锁(例如在同一个进程能多次获取同一把未解锁的锁)。
自旋锁的API
1.定义自旋锁
spinlock_t lock;
2.初始化自旋锁
void spin_lock_init(spinlock_t *lock)
3.上锁
void spin_lock(spinlock_t *lock)
4.解锁
void spin_unlock(spinlock_t *lock)
自旋锁的实例
1.定义处 --定义自旋锁
2.在 init 函数中初始化自旋锁
3.在 open 函数中加锁 +修改标志位+解锁
4.在 close 函数中加锁 +修改标志位+解锁
不能在open中上锁,在close中解锁–因为这样保护的临界区是open read write close ,并且在read write中有禁止调用的接口。所以不可以这样写。
那我们可以定义一个标志位flag ,
操作代码
spinlock_t lock; // 定义自旋锁
int flags = 0; // 定义一个标志位
int mycdev_open(struct inode* inode, struct file* file)
{
spin_lock(&lock); // 上锁
if (flags != 0) {
spin_unlock(&lock); // 解锁
return -EBUSY;
}
flags = 1;
spin_unlock(&lock); // 解锁
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
spin_lock(&lock); // 上锁
flags = 0;
spin_unlock(&lock); // 解锁
return 0;
}
实例代码:
mecdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
//自旋锁---适合临界量比较小的情况
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 500; // 如果是0就采用动态申请,如果是>0采用静态指定
int minor = 0;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
spinlock_t lock; // 定义自旋锁
int flags = 0; // 定义一个标志位
int mycdev_open(struct inode* inode, struct file* file)
{
spin_lock(&lock); // 上锁
if (flags != 0) {
spin_unlock(&lock); // 解锁
return -EBUSY;
}
flags = 1;
spin_unlock(&lock); // 解锁
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy_to_user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy_from_user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
spin_lock(&lock); // 上锁
flags = 0;
spin_unlock(&lock); // 解锁
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配cdev的对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("cdev_alloc error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.cdev成员初始化
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
// 静态指定
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
} else {
// 动态申请
ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
if (ret) {
printk("alloc_chrdev_region error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("cdev_add error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class_create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = 0; i < COUNT; i++) {
dev = device_create(cls, NULL, MKDEV(major, minor + i), NULL, "%s%d", CNAME, i);
if (IS_ERR(dev)) {
printk("device_create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
// 初始化自旋锁
spin_lock_init(&lock);
return 0; /*****************千万不能忘记写!!!**********/
ERR5:
for (--i; i >= 0; i--) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
for (i = 0; i < COUNT; i++) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), COUNT);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
char s[128] = "i am test data access...\n";
if((fd = open("/dev/mycdev1",O_RDWR))==-1)
ERRLOG("open error");
write(fd,s,sizeof(s));
sleep(7);
memset(s,0,sizeof(s));
read(fd,s,sizeof(s));
printf("s = %s\n",s);
close(fd);
return 0;
}
现象:
信号量
什么是信号量
信号量:当一个进程获取到信号量之后,如果此时另外一个进程也想获取这个信号量,后一个进程处于休眠状态。
信号量的特点
1.对多核是有效的
2.在获取不到信号量的时候进程处于休眠状态(进程不消耗cpu资源)
3.信号量保护的临界区比较大,在信号量保护的临界区中就可以执行延时,耗时,甚至休眠的操作
4.信号量工作在进程上下文
5.信号量也不会导致死锁
6.信号量不会关闭抢占
信号量不能在中断中使用。进程上下文、中断上下文、只有这两个上下文。
信号量的API
1.定义信号量
struct semaphore sem;
2.初始化信号
void sema_init(struct semaphore *sem, int val)
//val一般初始化为1,表示只有一个进程能获取到资源,其他的进程获取不到休眠
3.获取信号量
void down(struct semaphore *sem) //让信号量的计数值减去1
int down_trylock(struct semaphore *sem);
//不会休眠,尝试获取信号量,如果获取成功返回0,如果获取不成功返回1
4.释放信号量
void up(struct semaphore *sem); //释放信号量,让值加1
信号量的使用实例
1.定义信号量—struct semaphore sem;//定义信号量
2.在 init 函数中初始化信号量 sema_init(&sem, 1); //写1有互斥效果
3.在 open 函数中获取信号量 if(down_trylock(&sem)){ return -EBUSY;}
4.在 close 函数中释放信号量 up(&sem);
mycdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
//信号量---适合临界区比较大的情况
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major = 500; // 如果是0就采用动态申请,如果是>0采用静态指定
int minor = 0;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
struct semaphore sem;//定义信号量
int mycdev_open(struct inode* inode, struct file* file)
{
if(down_trylock(&sem)){
return -EBUSY;
}
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy_to_user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy_from_user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
up(&sem);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配cdev的对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("cdev_alloc error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.cdev成员初始化
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
// 静态指定
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
} else {
// 动态申请
ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
if (ret) {
printk("alloc_chrdev_region error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("cdev_add error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class_create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = 0; i < COUNT; i++) {
dev = device_create(cls, NULL, MKDEV(major, minor + i), NULL, "%s%d", CNAME, i);
if (IS_ERR(dev)) {
printk("device_create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
// 初始化信号量
sema_init(&sem, 1); //写1有互斥效果
return 0; /*****************千万不能忘记写!!!**********/
ERR5:
for (--i; i >= 0; i--) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
for (i = 0; i < COUNT; i++) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), COUNT);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
char s[128] = "i am test data access...\n";
if((fd = open("/dev/mycdev1",O_RDWR))==-1)
ERRLOG("open error");
write(fd,s,sizeof(s));
sleep(7); // 加一个sleep为了让进程时间长一点,方便看现象
memset(s,0,sizeof(s));
read(fd,s,sizeof(s));
printf("s = %s\n",s);
close(fd);
return 0;
}
现象:
互斥体
什么是互斥体
互斥体:当一个进程获取到互斥体之后,如果此时另外一个进程也想获取这个互斥体,后一个进程处于休眠状态。互斥体又叫排它锁。
互斥体的特点
1.对多核是有效的
2.在获取不到互斥体的时候进程处于休眠状态(不消耗cpu资源)
3.互斥体保护的临界区比较大,在互斥体保护的临界区中就可以执行延时,耗时,甚至休眠的操作
4.互斥体工作在进程上下文
5.互斥体也不会导致死锁
6.互斥体不会关闭抢占
7.**互斥体在获取不到资源的时候不会立即进入休眠状态,会适当的等一会再进入休眠状态,**如果在等的期间解锁了,那它不会休眠了,直接运行。
(在进程上下文中,在不确定临界区大小的时候,优先选择互斥体)
互斥体的API
1.定义互斥体
struct mutex mutex;
2.初始化互斥体
mutex_init(&mutex);
3.获取互斥体
void mutex_lock(struct mutex *lock)
int mutex_trylock(struct mutex *lock)
//尝试获取互斥体,获取到返回1,获取不到返回0
4.释放互斥体
void mutex_unlock(struct mutex *lock)
互斥体的使用实例
mycdev.c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define COUNT 3
#define CNAME "mycdev"
struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct mutex mutex;
int mycdev_open(struct inode* inode, struct file* file)
{
if(!mutex_trylock(&mutex)){
return -EBUSY;
}
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy from user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
mutex_unlock(&mutex);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("cdev_alloc error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.cdev成员初始化
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
} else {
ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("cdev_add error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class_create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = 0; i < COUNT; i++) {
dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
if (IS_ERR(dev)) {
printk("device_create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
// 初始化互斥体
mutex_init(&mutex);
return 0; /*********************别忘记写!!************************/
ERR5:
for (--i; i >= 0; i--) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
for (i = 0; i < COUNT; i++) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), COUNT);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include <head.h>
int main(int argc,const char * argv[])
{
int fd;
char s[128] = "i am test data access...\n";
if((fd = open("/dev/mycdev1",O_RDWR))==-1)
ERRLOG("open error");
write(fd,s,sizeof(s));
sleep(7);
memset(s,0,sizeof(s));
read(fd,s,sizeof(s));
printf("s = %s\n",s);
close(fd);
return 0;
}
原子操作
原子操作的原理
原子操作:系统使用“原子”这个词来命名当前的操作,含义是把整个操作看成一个不可被分割的整体。
底层实现:在对原子变量操作的时候内核会保证只有一个处理器在操作这个变量,对原子变量的值修改过程,内核是通过内联汇编完成的。这两点保证了原子性。
内联汇编:在C代码中执行汇编的指令。
内联汇编,怎么实现的呢?如果在一个核上定义了一个普通变量,那这个变量首先会被放到内存上,如果要参与运算,我们把这个变量放到catch高速缓存上,再从高速缓存上取到寄存器上。只要有中间环节就有可能会出错,所以原子操作对这个变量的操作是把这个变量直接放到寄存器中。
typedef struct {
int counter;
} atomic_t;
原子操作的API
方式1:
1.定义并初始化原子变量
atomic_t atm = ATOMIC_INIT(1);
//赋值的时候必须要调用接口,不能 atomic_t aa; aa.counter = 10;这样写;这样写就又放内存、缓存了。应该调用接口,直接操作内联汇编的指令,对值进行修改。
2.获取原子变量
int atomic_dec_and_test(atomic_t *v)
将原子变量的值减去1,然后判断是否是0,如果是0表示获取锁成功,成功返回
真,否则获取锁失败,失败返回假
3.释放原子变量
atomic_inc(atomic_t *v)
让原子变量的值加1
方式2:
1.定义并初始化原子变量
atomic_t atm = ATOMIC_INIT(-1);
2.获取原子变量
int atomic_inc_and_test(atomic_t *v)
将原子变量的值加上1,然后判断是否是0,如果是0表示获取锁成功,成功返回
真,否则获取锁失败,失败返回假
3.释放原子变量
atomic_dec(atomic_t *v)
让原子变量的值减1
原子操作的实例
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define COUNT 3
#define CNAME "mycdev"
struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
atomic_t atm = ATOMIC_INIT(1);
int mycdev_open(struct inode* inode, struct file* file)
{
if(!atomic_dec_and_test(&atm)){
atomic_inc(&atm); // 在执行atomic_dec_and_test()的时候给它-1了,所以这里需要加回来
return -EBUSY; //否则在释放原子变量的时候找不到。
}
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t mycdev_read(struct file* file,
char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file* file,
const char __user* ubuf, size_t size, loff_t* offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
printk("copy from user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
atomic_inc(&atm);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int ret, i;
dev_t devno;
// 1.分配对象
cdev = cdev_alloc();
if (cdev == NULL) {
printk("cdev_alloc error\n");
ret = -ENOMEM;
goto ERR1;
}
// 2.cdev成员初始化
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) {
ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
} else {
ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
if (ret) {
printk("register_chrdev_region error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
// 4.注册
ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
if (ret) {
printk("cdev_add error\n");
goto ERR3;
}
// 5.自动创建设备节点
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls)) {
printk("class_create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for (i = 0; i < COUNT; i++) {
dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
if (IS_ERR(dev)) {
printk("device_create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0; /*********************别忘记写!!************************/
ERR5:
for (--i; i >= 0; i--) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
for (i = 0; i < COUNT; i++) {
device_destroy(cls, MKDEV(major, minor + i));
}
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(MKDEV(major, minor), COUNT);
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");