赞
踩
目录
并发就是多个“用户”同时访问同一个共享资源。Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原因:
(1)多线程并发访问,Linux是多任务系统。
(2)抢占式并发访问,就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。
(3)中断程序并发访问。
(4)SMP核间并发访问,多核CPU存在核间并发访问。
并发访问带来的问题就是竞争。所谓的临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问,也就是要保证临界区是原子访问的。如果多个进程同时操作临界区就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。
原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量:
- typedef struct {
- int counter;
- } atomic_t;
如果要使用原子操作 API 函数,首先要先定义一个 atomic_t 的变量,如下所示:
atomic_t a; //定义 a
也可以在定义原子变量的时候给原子变量赋初值,如下所示:
- atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0
- #define ATOMIC_INIT(i) { (i) }
原子变量有了,接下来就是对原子变量进行操作,比如读、写、增加、减少等等, Linux 内
核提供了大量的原子操作 API 函数:
以上API函数都是操作原子变量中的counter来实现目的的。
原子位操作 API 函数:
原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形
变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于
结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的
线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,在 Linux
内核中就是自旋锁。
当一个线程要访问某个共享资源的时候首先要先获取相应的锁, 锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。对于自旋锁而言,如果自旋锁正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态,线程 B 不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待锁可用。
自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。把自旋锁比作一个变量 a,变量 a=1 的时候表示共享资源可用,当 a=0的时候表示共享资源不可用。现在线程 A 要访问共享资源,发现 a=0(自旋锁被其他线程持有),那么线程 A 就会不断的查询 a 的值,直到 a=1。从这里我们可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。所以自旋锁适用于短时期的轻量级加锁,如果遇到需要长时间持有锁的场景那就需要换其他的方法了,这个我们后面会讲解。Linux 内核使用结构体 spinlock_t 表示自旋锁:
- typedef struct spinlock {
- union {
- struct raw_spinlock rlock;
-
- #ifdef CONFIG_DEBUG_LOCK_ALLOC
- # define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
- struct {
- u8 __padding[LOCK_PADSIZE];
- struct lockdep_map dep_map;
- };
- #endif
- };
- } spinlock_t;
在使用自旋锁之前,肯定要先定义一个自旋锁变量,定义方法如下所示:
spinlock_t lock; //定义自旋锁
自旋锁的API函数:
上述API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。自旋锁会自动禁止抢占,也就说当线程 A得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,好了,死锁发生了!如果此时中断也要插一脚,中断也想访问共享资源,那该怎么办呢?首先可以肯定的是,中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(也就是本 CPU 中断,对于多核 SOC来说会有多个 CPU 核),否则可能导致锁死现象的发生:
一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock,示例代码如下所示:
下半部(BH)也会竞争共享资源,有些资料也会将下半部叫做底半部。
其他类型的锁:
(1)读写自旋锁
- typedef struct {
- arch_rwlock_t raw_lock;
- #ifdef CONFIG_GENERIC_LOCKBREAK
- unsigned int break_lock;
- #endif
- #ifdef CONFIG_DEBUG_SPINLOCK
- unsigned int magic, owner_cpu;
- void *owner;
- #endif
- #ifdef CONFIG_DEBUG_LOCK_ALLOC
- struct lockdep_map dep_map;
- #endif
- } rwlock_t;
(2)顺序锁
- typedef struct {
- struct seqcount seqcount;
- spinlock_t lock;
- } seqlock_t;
自旋锁使用注意事项:
(1)因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如稍后要讲的信号量和互斥体。
(2)自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
(3)不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了!
(4)在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。
Linux 内核也提供了信号量机制,信号量常常用于控制对共享资源的访问。相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、 C 合租了一套房子,这个房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。 B 要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。 B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉,这个时候相当于信号量。可以看出,使用信号量会提高处理器的使用效率,毕竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使进程进入休眠状态以后会进行进程上下文切换,切换就会有开销。信号量特点:
(1)因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
(2)因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
(3)如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
Linux 内核使用 semaphore 结构体表示信号量,结构体内容如下所示:
- struct semaphore {
- raw_spinlock_t lock;
- unsigned int count;
- struct list_head wait_list;
- };
要想使用信号量就得先定义,然后初始化信号量。有关信号量的 API 函数如下:
- #define DEFINE_SEMAPHORE(name) \
- struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
-
- static inline void sema_init(struct semaphore *sem, int val)
- {
- static struct lock_class_key __key;
- *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
- lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
- }
-
- void down(struct semaphore *sem)
- {
- unsigned long flags;
-
- raw_spin_lock_irqsave(&sem->lock, flags);
- if (likely(sem->count > 0))
- sem->count--;
- else
- __down(sem);
- raw_spin_unlock_irqrestore(&sem->lock, flags);
- }
-
- int down_trylock(struct semaphore *sem)
- {
- unsigned long flags;
- int count;
-
- raw_spin_lock_irqsave(&sem->lock, flags);
- count = sem->count - 1;
- if (likely(count >= 0))
- sem->count = count;
- raw_spin_unlock_irqrestore(&sem->lock, flags);
-
- return (count < 0);
- }
-
- int down_interruptible(struct semaphore *sem)
- {
- unsigned long flags;
- int result = 0;
-
- raw_spin_lock_irqsave(&sem->lock, flags);
- if (likely(sem->count > 0))
- sem->count--;
- else
- result = __down_interruptible(sem);
- raw_spin_unlock_irqrestore(&sem->lock, flags);
-
- return result;
- }
-
- void up(struct semaphore *sem)
- {
- unsigned long flags;
-
- raw_spin_lock_irqsave(&sem->lock, flags);
- if (likely(list_empty(&sem->wait_list)))
- sem->count++;
- else
- __up(sem);
- raw_spin_unlock_irqrestore(&sem->lock, flags);
- }
- EXPORT_SYMBOL(up);
其中__down和__down_interruptible的区别:
- static noinline void __sched __down(struct semaphore *sem)
- {
- __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
- }
-
- static noinline int __sched __down_interruptible(struct semaphore *sem)
- {
- return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
- }
信号量的使用如下所示:
将信号量的值设置为 1 就可以使用信号量进行互斥访问了,虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。互斥访问表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。Linux 内核使用 mutex 结构体表示互斥体,定义如下:
- struct mutex {
- /* 1: unlocked, 0: locked, negative: locked, possible waiters */
- atomic_t count;
- spinlock_t wait_lock;
- struct list_head wait_list;
- #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
- struct task_struct *owner;
- #endif
- #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
- struct optimistic_spin_queue osq; /* Spinner MCS lock */
- #endif
- #ifdef CONFIG_DEBUG_MUTEXES
- void *magic;
- #endif
- #ifdef CONFIG_DEBUG_LOCK_ALLOC
- struct lockdep_map dep_map;
- #endif
- };
在使用 mutex 的时候要注意如下几点:
①、 mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
②、和信号量一样, mutex 保护的临界区可以调用引起阻塞的 API 函数。
③、因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并
且 mutex 不能递归上锁和解锁。
有关互斥体的 API 函数如下:
- #define DEFINE_MUTEX(mutexname) \
- struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
-
- void
- __mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
- {
- atomic_set(&lock->count, 1);
- spin_lock_init(&lock->wait_lock);
- INIT_LIST_HEAD(&lock->wait_list);
- mutex_clear_owner(lock);
- #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
- osq_lock_init(&lock->osq);
- #endif
-
- debug_mutex_init(lock, name, key);
- }
-
- void __sched mutex_lock(struct mutex *lock)
- {
- might_sleep();
- /*
- * The locking fastpath is the 1->0 transition from
- * 'unlocked' into 'locked' state.
- */
- __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
- mutex_set_owner(lock);
- }
-
- void __sched mutex_unlock(struct mutex *lock)
- {
- /*
- * The unlocking fastpath is the 0->1 transition from 'locked'
- * into 'unlocked' state:
- */
- #ifndef CONFIG_DEBUG_MUTEXES
- /*
- * When debugging is enabled we must not clear the owner before time,
- * the slow path will always be taken, and that clears the owner field
- * after verifying that it was indeed current.
- */
- mutex_clear_owner(lock);
- #endif
- __mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
- }
-
- int __sched mutex_trylock(struct mutex *lock)
- {
- int ret;
-
- ret = __mutex_fastpath_trylock(&lock->count, __mutex_trylock_slowpath);
- if (ret)
- mutex_set_owner(lock);
-
- return ret;
- }
-
- static inline int mutex_is_locked(struct mutex *lock)
- {
- return atomic_read(&lock->count) != 1;
- }
- int __sched mutex_lock_interruptible(struct mutex *lock)
- {
- int ret;
-
- might_sleep();
- ret = __mutex_fastpath_lock_retval(&lock->count);
- if (likely(!ret)) {
- mutex_set_owner(lock);
- return 0;
- } else
- return __mutex_lock_interruptible_slowpath(lock);
- }
互斥体的使用如下所示:
本节实验重点就是使用atomic 来实现一次只能允许一个应用访问 LED。
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/delay.h>
- #include <linux/ide.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/errno.h>
- #include <linux/gpio.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #include <asm/mach/map.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
-
- #define GPIOLED_CNT 1 /* 设备号个数 */
- #define GPIOLED_NAME "gpioled" /* 名字 */
- #define LEDOFF 0 /* 关灯 */
- #define LEDON 1 /* 开灯 */
-
-
- /* gpioled设备结构体 */
- struct gpioled_dev{
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *device; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- struct device_node *nd; /* 设备节点 */
- int led_gpio; /* led所使用的GPIO编号 */
- atomic_t lock; /* 原子变量 */
- };
-
- struct gpioled_dev gpioled; /* led设备 */
-
- /*
- * @description : 打开设备
- * @param - inode : 传递给驱动的inode
- * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- * 一般在open的时候将private_data指向设备结构体。
- * @return : 0 成功;其他 失败
- */
- static int led_open(struct inode *inode, struct file *filp)
- {
- /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */
- if (!atomic_dec_and_test(&gpioled.lock)) {
- atomic_inc(&gpioled.lock); /* 小于0的话就加1,使其原子变量等于0 */
- return -EBUSY; /* LED被使用,返回忙 */
- }
-
- filp->private_data = &gpioled; /* 设置私有数据 */
- return 0;
- }
-
- /*
- * @description : 从设备读取数据
- * @param - filp : 要打开的设备文件(文件描述符)
- * @param - buf : 返回给用户空间的数据缓冲区
- * @param - cnt : 要读取的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 读取的字节数,如果为负值,表示读取失败
- */
- static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
- {
- return 0;
- }
-
- /*
- * @description : 向设备写数据
- * @param - filp : 设备文件,表示打开的文件描述符
- * @param - buf : 要写给设备写入的数据
- * @param - cnt : 要写入的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 写入的字节数,如果为负值,表示写入失败
- */
- static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
- {
- int retvalue;
- unsigned char databuf[1];
- unsigned char ledstat;
- struct gpioled_dev *dev = filp->private_data;
-
- retvalue = copy_from_user(databuf, buf, cnt);
- if(retvalue < 0) {
- printk("kernel write failed!\r\n");
- return -EFAULT;
- }
-
- ledstat = databuf[0]; /* 获取状态值 */
-
- if(ledstat == LEDON) {
- gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
- } else if(ledstat == LEDOFF) {
- gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
- }
- return 0;
- }
-
- /*
- * @description : 关闭/释放设备
- * @param - filp : 要关闭的设备文件(文件描述符)
- * @return : 0 成功;其他 失败
- */
- static int led_release(struct inode *inode, struct file *filp)
- {
- struct gpioled_dev *dev = filp->private_data;
-
- /* 关闭驱动文件的时候释放原子变量 */
- atomic_inc(&dev->lock);
- return 0;
- }
-
- /* 设备操作函数 */
- static struct file_operations gpioled_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- /*
- * @description : 驱动入口函数
- * @param : 无
- * @return : 无
- */
- static int __init led_init(void)
- {
- int ret = 0;
-
- /* 初始化原子变量 */
- atomic_set(&gpioled.lock, 1); /* 原子变量初始值为1 */
-
- /* 设置LED所使用的GPIO */
- /* 1、获取设备节点:gpioled */
- gpioled.nd = of_find_node_by_path("/gpioled");
- if(gpioled.nd == NULL) {
- printk("gpioled node not find!\r\n");
- return -EINVAL;
- } else {
- printk("gpioled node find!\r\n");
- }
-
- /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
- gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
- if(gpioled.led_gpio < 0) {
- printk("can't get led-gpio");
- return -EINVAL;
- }
- printk("led-gpio num = %d\r\n", gpioled.led_gpio);
-
- /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
- ret = gpio_direction_output(gpioled.led_gpio, 1);
- if(ret < 0) {
- printk("can't set gpio!\r\n");
- }
-
- /* 注册字符设备驱动 */
- /* 1、创建设备号 */
- if (gpioled.major) { /* 定义了设备号 */
- gpioled.devid = MKDEV(gpioled.major, 0);
- register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
- } else { /* 没有定义设备号 */
- alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
- gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
- gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
- }
- printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
-
- /* 2、初始化cdev */
- gpioled.cdev.owner = THIS_MODULE;
- cdev_init(&gpioled.cdev, &gpioled_fops);
-
- /* 3、添加一个cdev */
- cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
-
- /* 4、创建类 */
- gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
- if (IS_ERR(gpioled.class)) {
- return PTR_ERR(gpioled.class);
- }
-
- /* 5、创建设备 */
- gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
- if (IS_ERR(gpioled.device)) {
- return PTR_ERR(gpioled.device);
- }
-
- return 0;
- }
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static void __exit led_exit(void)
- {
- /* 注销字符设备驱动 */
- cdev_del(&gpioled.cdev);/* 删除cdev */
- unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
-
- device_destroy(gpioled.class, gpioled.devid);
- class_destroy(gpioled.class);
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("wrw");
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/delay.h>
- #include <linux/ide.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/errno.h>
- #include <linux/gpio.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #include <asm/mach/map.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
-
- #define GPIOLED_CNT 1 /* 设备号个数 */
- #define GPIOLED_NAME "gpioled" /* 名字 */
- #define LEDOFF 0 /* 关灯 */
- #define LEDON 1 /* 开灯 */
-
-
- /* gpioled设备结构体 */
- struct gpioled_dev{
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *device; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- struct device_node *nd; /* 设备节点 */
- int led_gpio; /* led所使用的GPIO编号 */
- int dev_stats; /* 设备使用状态,0,设备未使用;>0,设备已经被使用 */
- spinlock_t lock; /* 自旋锁 */
- };
-
- struct gpioled_dev gpioled; /* led设备 */
-
- /*
- * @description : 打开设备
- * @param - inode : 传递给驱动的inode
- * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- * 一般在open的时候将private_data指向设备结构体。
- * @return : 0 成功;其他 失败
- */
- static int led_open(struct inode *inode, struct file *filp)
- {
- unsigned long flags;
- filp->private_data = &gpioled; /* 设置私有数据 */
-
- spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */
- if (gpioled.dev_stats) { /* 如果设备被使用了 */
- spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */
- return -EBUSY;
- }
- gpioled.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
- spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */
-
- return 0;
- }
-
- /*
- * @description : 从设备读取数据
- * @param - filp : 要打开的设备文件(文件描述符)
- * @param - buf : 返回给用户空间的数据缓冲区
- * @param - cnt : 要读取的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 读取的字节数,如果为负值,表示读取失败
- */
- static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
- {
- return 0;
- }
-
- /*
- * @description : 向设备写数据
- * @param - filp : 设备文件,表示打开的文件描述符
- * @param - buf : 要写给设备写入的数据
- * @param - cnt : 要写入的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 写入的字节数,如果为负值,表示写入失败
- */
- static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
- {
- int retvalue;
- unsigned char databuf[1];
- unsigned char ledstat;
- struct gpioled_dev *dev = filp->private_data;
-
- retvalue = copy_from_user(databuf, buf, cnt);
- if(retvalue < 0) {
- printk("kernel write failed!\r\n");
- return -EFAULT;
- }
-
- ledstat = databuf[0]; /* 获取状态值 */
-
- if(ledstat == LEDON) {
- gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
- } else if(ledstat == LEDOFF) {
- gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
- }
- return 0;
- }
-
- /*
- * @description : 关闭/释放设备
- * @param - filp : 要关闭的设备文件(文件描述符)
- * @return : 0 成功;其他 失败
- */
- static int led_release(struct inode *inode, struct file *filp)
- {
- unsigned long flags;
- struct gpioled_dev *dev = filp->private_data;
-
- /* 关闭驱动文件的时候将dev_stats减1 */
- spin_lock_irqsave(&dev->lock, flags); /* 上锁 */
- if (dev->dev_stats) {
- dev->dev_stats--;
- }
- spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */
-
- return 0;
- }
-
- /* 设备操作函数 */
- static struct file_operations gpioled_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- /*
- * @description : 驱动入口函数
- * @param : 无
- * @return : 无
- */
- static int __init led_init(void)
- {
- int ret = 0;
-
- /* 初始化自旋锁 */
- spin_lock_init(&gpioled.lock);
-
- /* 设置LED所使用的GPIO */
- /* 1、获取设备节点:gpioled */
- gpioled.nd = of_find_node_by_path("/gpioled");
- if(gpioled.nd == NULL) {
- printk("gpioled node not find!\r\n");
- return -EINVAL;
- } else {
- printk("gpioled node find!\r\n");
- }
-
- /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
- gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
- if(gpioled.led_gpio < 0) {
- printk("can't get led-gpio");
- return -EINVAL;
- }
- printk("led-gpio num = %d\r\n", gpioled.led_gpio);
-
- /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
- ret = gpio_direction_output(gpioled.led_gpio, 1);
- if(ret < 0) {
- printk("can't set gpio!\r\n");
- }
-
- /* 注册字符设备驱动 */
- /* 1、创建设备号 */
- if (gpioled.major) { /* 定义了设备号 */
- gpioled.devid = MKDEV(gpioled.major, 0);
- register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
- } else { /* 没有定义设备号 */
- alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
- gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
- gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
- }
- printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
-
- /* 2、初始化cdev */
- gpioled.cdev.owner = THIS_MODULE;
- cdev_init(&gpioled.cdev, &gpioled_fops);
-
- /* 3、添加一个cdev */
- cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
-
- /* 4、创建类 */
- gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
- if (IS_ERR(gpioled.class)) {
- return PTR_ERR(gpioled.class);
- }
-
- /* 5、创建设备 */
- gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
- if (IS_ERR(gpioled.device)) {
- return PTR_ERR(gpioled.device);
- }
-
- return 0;
- }
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static void __exit led_exit(void)
- {
- /* 注销字符设备驱动 */
- cdev_del(&gpioled.cdev);/* 删除cdev */
- unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
-
- device_destroy(gpioled.class, gpioled.devid);
- class_destroy(gpioled.class);
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("WRW");
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/delay.h>
- #include <linux/ide.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/errno.h>
- #include <linux/gpio.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #include <linux/semaphore.h>
- #include <asm/mach/map.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
-
- #define GPIOLED_CNT 1 /* 设备号个数 */
- #define GPIOLED_NAME "gpioled" /* 名字 */
- #define LEDOFF 0 /* 关灯 */
- #define LEDON 1 /* 开灯 */
-
-
- /* gpioled设备结构体 */
- struct gpioled_dev{
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *device; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- struct device_node *nd; /* 设备节点 */
- int led_gpio; /* led所使用的GPIO编号 */
- struct semaphore sem; /* 信号量 */
- };
-
- struct gpioled_dev gpioled; /* led设备 */
-
- /*
- * @description : 打开设备
- * @param - inode : 传递给驱动的inode
- * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- * 一般在open的时候将private_data指向设备结构体。
- * @return : 0 成功;其他 失败
- */
- static int led_open(struct inode *inode, struct file *filp)
- {
- filp->private_data = &gpioled; /* 设置私有数据 */
-
- /* 获取信号量 */
- if (down_interruptible(&gpioled.sem)) { /* 获取信号量,进入休眠状态的进程可以被信号打断 */
- return -ERESTARTSYS;
- }
- #if 0
- down(&gpioled.sem); /* 不能被信号打断 */
- #endif
-
- return 0;
- }
-
- /*
- * @description : 从设备读取数据
- * @param - filp : 要打开的设备文件(文件描述符)
- * @param - buf : 返回给用户空间的数据缓冲区
- * @param - cnt : 要读取的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 读取的字节数,如果为负值,表示读取失败
- */
- static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
- {
- return 0;
- }
-
- /*
- * @description : 向设备写数据
- * @param - filp : 设备文件,表示打开的文件描述符
- * @param - buf : 要写给设备写入的数据
- * @param - cnt : 要写入的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 写入的字节数,如果为负值,表示写入失败
- */
- static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
- {
- int retvalue;
- unsigned char databuf[1];
- unsigned char ledstat;
- struct gpioled_dev *dev = filp->private_data;
-
- retvalue = copy_from_user(databuf, buf, cnt);
- if(retvalue < 0) {
- printk("kernel write failed!\r\n");
- return -EFAULT;
- }
-
- ledstat = databuf[0]; /* 获取状态值 */
-
- if(ledstat == LEDON) {
- gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
- } else if(ledstat == LEDOFF) {
- gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
- }
- return 0;
- }
-
- /*
- * @description : 关闭/释放设备
- * @param - filp : 要关闭的设备文件(文件描述符)
- * @return : 0 成功;其他 失败
- */
- static int led_release(struct inode *inode, struct file *filp)
- {
- struct gpioled_dev *dev = filp->private_data;
-
- up(&dev->sem); /* 释放信号量,信号量值加1 */
-
- return 0;
- }
-
- /* 设备操作函数 */
- static struct file_operations gpioled_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- /*
- * @description : 驱动入口函数
- * @param : 无
- * @return : 无
- */
- static int __init led_init(void)
- {
- int ret = 0;
-
- /* 初始化信号量 */
- sema_init(&gpioled.sem, 1);
-
- /* 设置LED所使用的GPIO */
- /* 1、获取设备节点:gpioled */
- gpioled.nd = of_find_node_by_path("/gpioled");
- if(gpioled.nd == NULL) {
- printk("gpioled node not find!\r\n");
- return -EINVAL;
- } else {
- printk("gpioled node find!\r\n");
- }
-
- /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
- gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
- if(gpioled.led_gpio < 0) {
- printk("can't get led-gpio");
- return -EINVAL;
- }
- printk("led-gpio num = %d\r\n", gpioled.led_gpio);
-
- /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
- ret = gpio_direction_output(gpioled.led_gpio, 1);
- if(ret < 0) {
- printk("can't set gpio!\r\n");
- }
-
- /* 注册字符设备驱动 */
- /* 1、创建设备号 */
- if (gpioled.major) { /* 定义了设备号 */
- gpioled.devid = MKDEV(gpioled.major, 0);
- register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
- } else { /* 没有定义设备号 */
- alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
- gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
- gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
- }
- printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
-
- /* 2、初始化cdev */
- gpioled.cdev.owner = THIS_MODULE;
- cdev_init(&gpioled.cdev, &gpioled_fops);
-
- /* 3、添加一个cdev */
- cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
-
- /* 4、创建类 */
- gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
- if (IS_ERR(gpioled.class)) {
- return PTR_ERR(gpioled.class);
- }
-
- /* 5、创建设备 */
- gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
- if (IS_ERR(gpioled.device)) {
- return PTR_ERR(gpioled.device);
- }
-
- return 0;
- }
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static void __exit led_exit(void)
- {
- /* 注销字符设备驱动 */
- cdev_del(&gpioled.cdev);/* 删除cdev */
- unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
-
- device_destroy(gpioled.class, gpioled.devid);
- class_destroy(gpioled.class);
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("wrw");
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/delay.h>
- #include <linux/ide.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/errno.h>
- #include <linux/gpio.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #include <linux/semaphore.h>
- #include <asm/mach/map.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
-
- #define GPIOLED_CNT 1 /* 设备号个数 */
- #define GPIOLED_NAME "gpioled" /* 名字 */
- #define LEDOFF 0 /* 关灯 */
- #define LEDON 1 /* 开灯 */
-
-
- /* gpioled设备结构体 */
- struct gpioled_dev{
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *device; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- struct device_node *nd; /* 设备节点 */
- int led_gpio; /* led所使用的GPIO编号 */
- struct mutex lock; /* 互斥体 */
- };
-
- struct gpioled_dev gpioled; /* led设备 */
-
- /*
- * @description : 打开设备
- * @param - inode : 传递给驱动的inode
- * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- * 一般在open的时候将private_data指向设备结构体。
- * @return : 0 成功;其他 失败
- */
- static int led_open(struct inode *inode, struct file *filp)
- {
- filp->private_data = &gpioled; /* 设置私有数据 */
-
- /* 获取互斥体,可以被信号打断 */
- if (mutex_lock_interruptible(&gpioled.lock)) {
- return -ERESTARTSYS;
- }
- #if 0
- mutex_lock(&gpioled.lock); /* 不能被信号打断 */
- #endif
-
- return 0;
- }
-
- /*
- * @description : 从设备读取数据
- * @param - filp : 要打开的设备文件(文件描述符)
- * @param - buf : 返回给用户空间的数据缓冲区
- * @param - cnt : 要读取的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 读取的字节数,如果为负值,表示读取失败
- */
- static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
- {
- return 0;
- }
-
- /*
- * @description : 向设备写数据
- * @param - filp : 设备文件,表示打开的文件描述符
- * @param - buf : 要写给设备写入的数据
- * @param - cnt : 要写入的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 写入的字节数,如果为负值,表示写入失败
- */
- static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
- {
- int retvalue;
- unsigned char databuf[1];
- unsigned char ledstat;
- struct gpioled_dev *dev = filp->private_data;
-
- retvalue = copy_from_user(databuf, buf, cnt);
- if(retvalue < 0) {
- printk("kernel write failed!\r\n");
- return -EFAULT;
- }
-
- ledstat = databuf[0]; /* 获取状态值 */
-
- if(ledstat == LEDON) {
- gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
- } else if(ledstat == LEDOFF) {
- gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
- }
- return 0;
- }
-
- /*
- * @description : 关闭/释放设备
- * @param - filp : 要关闭的设备文件(文件描述符)
- * @return : 0 成功;其他 失败
- */
- static int led_release(struct inode *inode, struct file *filp)
- {
- struct gpioled_dev *dev = filp->private_data;
-
- /* 释放互斥锁 */
- mutex_unlock(&dev->lock);
-
- return 0;
- }
-
- /* 设备操作函数 */
- static struct file_operations gpioled_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- /*
- * @description : 驱动入口函数
- * @param : 无
- * @return : 无
- */
- static int __init led_init(void)
- {
- int ret = 0;
-
- /* 初始化互斥体 */
- mutex_init(&gpioled.lock);
-
- /* 设置LED所使用的GPIO */
- /* 1、获取设备节点:gpioled */
- gpioled.nd = of_find_node_by_path("/gpioled");
- if(gpioled.nd == NULL) {
- printk("gpioled node not find!\r\n");
- return -EINVAL;
- } else {
- printk("gpioled node find!\r\n");
- }
-
- /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
- gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
- if(gpioled.led_gpio < 0) {
- printk("can't get led-gpio");
- return -EINVAL;
- }
- printk("led-gpio num = %d\r\n", gpioled.led_gpio);
-
- /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
- ret = gpio_direction_output(gpioled.led_gpio, 1);
- if(ret < 0) {
- printk("can't set gpio!\r\n");
- }
-
- /* 注册字符设备驱动 */
- /* 1、创建设备号 */
- if (gpioled.major) { /* 定义了设备号 */
- gpioled.devid = MKDEV(gpioled.major, 0);
- register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
- } else { /* 没有定义设备号 */
- alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
- gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
- gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
- }
- printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
-
- /* 2、初始化cdev */
- gpioled.cdev.owner = THIS_MODULE;
- cdev_init(&gpioled.cdev, &gpioled_fops);
-
- /* 3、添加一个cdev */
- cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
-
- /* 4、创建类 */
- gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
- if (IS_ERR(gpioled.class)) {
- return PTR_ERR(gpioled.class);
- }
-
- /* 5、创建设备 */
- gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
- if (IS_ERR(gpioled.device)) {
- return PTR_ERR(gpioled.device);
- }
-
- return 0;
- }
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static void __exit led_exit(void)
- {
- /* 注销字符设备驱动 */
- cdev_del(&gpioled.cdev);/* 删除cdev */
- unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
-
- device_destroy(gpioled.class, gpioled.devid);
- class_destroy(gpioled.class);
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("wrw");
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。