韦东山2440-学习笔记-字符设备驱动
button字符驱动
#include <linux/module.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "button"
static void button_timeout(unsigned long data);
dev_t dev;
struct cdev *cdev;
struct class *button_class;
static wait_queue_head_t button_press_wait;
static int button_press;
static unsigned int g_value;
static struct fasync_struct *button_async_queue;
static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0); /* 用于消除按键抖动 */
// 定义原子变量,实现只能一个进程打开
static atomic_t can_open = ATOMIC_INIT(1);
struct button_desc {
unsigned int pin;
unsigned int val;
unsigned int irq;
};
static struct button_desc button_desc_arr[3] = {
{ .pin = S3C2410_GPF0, .val = 0x01, .irq = IRQ_EINT0 },
{ .pin = S3C2410_GPF2, .val = 0x02, .irq = IRQ_EINT2 },
{ .pin = S3C2410_GPG3, .val = 0x03, .irq = IRQ_EINT11 },
};
static void
button_timeout(unsigned long data)
{
int up;
struct button_desc *bd = (struct button_desc *)data;
// 从系统删除timer
del_timer(&button_timer);
button_timer.data = 0; // 重置
g_value = bd->val;
up = s3c2410_gpio_getpin(bd->pin);
if (up)
g_value |= 0x80;
/* 唤醒button_press_wait 上的进程 */
button_press = 1;
wake_up_interruptible(&button_press_wait);
/* 异步通知进程 */
kill_fasync(&button_async_queue, SIGIO, POLL_IN);
}
static irqreturn_t
buttons_irq_handler(int irq, void *dev_id)
{
if (button_timer.data != 0) // 避免多次中断导致重复添加定时器
return IRQ_HANDLED;
// 设置定时器,延迟读取按键
button_timer.expires = jiffies + msecs_to_jiffies(100); // 100ms 后读取按键
button_timer.data = (unsigned long)dev_id;
mod_timer(&button_timer, button_timer.expires); // 将timer加入系统时间堆
return IRQ_HANDLED;
}
static int
button_fasync(int fd, struct file *file, int on)
{
return fasync_helper(fd, file, on, &button_async_queue);
}
static int
button_open (struct inode *inode, struct file *fp)
{
unsigned int i;
if (!atomic_dec_and_test(&can_open)) {
atomic_inc(&can_open);
return -EBUSY;
}
/* 设置gpio为中断模式 */
s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);
s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);
/* 注册中断, IRQT_BOTHEDGE : 双边缘触发 */
for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
request_irq(button_desc_arr[i].irq, buttons_irq_handler,
IRQT_BOTHEDGE, DEVICE_NAME, button_desc_arr + i);
}
return 0;
}
static ssize_t
button_read (struct file *fp, char __user *user, size_t sz, loff_t *poffst)
{
if (fp->f_flags & O_NONBLOCK) { /* 如果是非阻塞操作 */
if (!button_press)
return -EAGAIN;
}
/* 若没有数据,button_press 为0,则挂起本进程到等待队列 button_press_wait */
wait_event_interruptible(button_press_wait, button_press);
/* 重置 button_press */
button_press = 0;
if (copy_to_user(user, &g_value, sz)) {
printk(DEVICE_NAME " failed to copy_to_user");
return -1;
}
return 0;
}
static int
button_close (struct inode *inode, struct file *fp)
{
unsigned int i;
atomic_inc(&can_open);
for (i = 0; i < sizeof(button_desc_arr)/ sizeof(*button_desc_arr); i++) {
free_irq(button_desc_arr[i].irq, button_desc_arr + i);
}
return 0;
}
static unsigned int
button_poll (struct file *file, struct poll_table_struct *wait)
{
// 将本进程加入 button_press_wait 等待队列,但当前不会阻塞
poll_wait(file, &button_press_wait, wait);
if (button_press) // 这里不重置 button_press 因为 数据还没有读,在read后重置 button_press
return POLLIN;
return 0;
}
static const struct file_operations button_ops = {
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
.poll = button_poll,
.release = button_close,
.fasync = button_fasync,
};
static int __init
button_init(void)
{
unsigned int major;
/* 1. 字符设备驱动框架部分 */
/* 1.1 申请设备号 */
if (alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME) < 0) {
printk(DEVICE_NAME "failed to alloc dev\n");
return -1;
}
/* 1.2 构建cdev,并绑定 file_operations */
cdev = cdev_alloc();
cdev->ops = &button_ops;
cdev->owner = THIS_MODULE;
/* 1.3 将cdev加到系统的哈希表中,如此才能通过设备号找到cdev */
if (cdev_add(cdev, dev, 1) < 0) {
printk(DEVICE_NAME "failed to cdev_add\n");
return -1;
}
/* 2. 为了mdev自动创建设备节点 */
/* 2.1 创建一个/sys/class/button 类 */
button_class = class_create(THIS_MODULE, DEVICE_NAME);
/* 2.2 创建/sys/class/button/buttons */
major = MAJOR(dev);
device_create(button_class, NULL, MKDEV(major, 0), "buttons");
/* 3. 初始化等待队列,以实现阻塞操作 */
init_waitqueue_head(&button_press_wait);
init_timer(&button_timer);
return 0;
}
static void __exit
button_exit(void)
{
int major;
major = MAJOR(dev);
device_destroy(button_class, MKDEV(major, 0));
class_destroy(button_class);
cdev_del(cdev);
unregister_chrdev_region(dev, 4);
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
poll
poll 机制分析
应用程序调用poll时,会遍历所有 fd对应 file->f_op->poll(),
驱动模块的poll将当前进程加入等待队列,并检查是否有事件,返回事件个数,
当没有事件时,陷入睡眠
sys_poll
do_sys_poll(ufds, nfds, &timeout_jiffies) // timeout_jiffies 是输入输出参数,输入超时值,输出剩余的时间
poll_initwait(&table);
init_poll_funcptr(&pwq->pt, __pollwait); // -> table->pt->qproc = __pollwait;
i = nfds; while(i != 0) {...} // 从用户空间中将poll event参数拷贝到内核空间
fdcount = do_poll(nfds, head, &table, timeout);
for (;;) {
if (do_pollfd(pfd, pt)) { // -> mask = file->f_op->poll(file, pwait);
count++; // 调用驱动的 poll函数,如果mask>0则 count++
pt = NULL;
}
// 如果 count > 0 ,有事件,返回
// 如果 *timeout == 0 ,超时,返回
// 有信号待处理,返回
if (count || !*timeout || signal_pending(current))
break;
// 睡眠__timeout 时间, 可以被打断
__timeout = schedule_timeout(__timeout);
}
return count; // 返回事件数量
while() {...} // 将发生的事件拷贝到用户空间
驱动的poll函数
static unsigned int evdev_poll(struct file *file, poll_table *wait)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
// 调用 poll_wait , 最终将当前进程加入 驱动构建的等待队列, 但当前不会阻塞,在do_poll中若没有事件,则调用 schedule_timeout 阻塞
poll_wait(file, &evdev->wait, wait); // > p->qproc(filp, wait_address, p);
// > __pollwait(filp, wait_address, p);
init_waitqueue_entry(&entry->wait, current);
add_wait_queue(wait_address, &entry->wait);
return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) |
(evdev->exist ? 0 : (POLLHUP | POLLERR)); // 返回 POLLIN 等事件
}
等待队列的函数
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wake_up_interruptible(&wait);
驱动如何使用poll机制 ?
1) file_operations 中定义 poll ,准备等待队列 waitq
2) poll(struct file *file, poll_table *wait_table) {
poll_wait(file, &waitq, wait_table); // 将本进程加入等待队列
// 3) 判断释放有事件
return event; // 无事件为0,有事件返回 POLLIN POLLOUT 等
}
poll 和 select 在驱动层面都是调用 f_op->poll ,所以驱动实现了poll,应用层可以调用poll和 select
给button驱动加上 poll
static unsigned int
button_poll (struct file *file, struct poll_table_struct *wait)
{
// 将本进程加入 button_press_wait 等待队列,但当前不会阻塞
poll_wait(file, &button_press_wait, wait);
if (button_press) // 这里不重置 button_press 因为 数据还没有读,在read后重置 button_press
return POLLIN;
return 0;
}
异步通知
内核主动通知应用程序有事件,需要应用程序把自己的进程号注册到驱动模块的异步通知队列,
当事件发生时,驱动模块 kill_fasync 给所有已注册进程发信号 SIGIO
- 应用程序调用 f_ops->fasync 将自己的pid注册进驱动的异步通知队列
int olfags;
fcntl(fd, F_SETOWN, getpid());
olfags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, olfags | FASYNC);
signal(SIGIO, signal_io_handler);
- 当事件发生时,驱动调用 kill_fasync给已注册的进程发送信号SIGIO
static irqreturn_t
buttons_irq_handler(int irq, void *dev_id)
{
struct button_desc *bd = dev_id;
unsigned int up;
g_value = bd->val;
up = s3c2410_gpio_getpin(bd->pin);
if (up)
g_value |= 0x80;
/* 唤醒button_press_wait 上的进程 */
button_press = 1;
wake_up_interruptible(&button_press_wait);
kill_fasync(&button_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
static int
button_fasync(int fd, struct file *file, int on)
{
return fasync_helper(fd, file, on, &button_async_queue);
}
static const struct file_operations button_ops = {
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
.poll = button_poll,
.release = button_close,
.fasync = button_fasync,
};
原子操作实现互斥
atomic_t v = ATOMIC_INIT(0)
atomic_dec_and_test(atomic_t *)
atomic_inc(atomic_t *);
使用原子操作实现互斥,
定义一个原子变量作为资源计数,
获得资源时需要 atomic_dec_and_test,如果没有资源则atomic_inc 并退出。
进程退出时要释放资源 atomic_inc
比如实现 button驱动只能一个进程打开
// 定义原子变量,实现只能一个进程打开
static atomic_t can_open = ATOMIC_INIT(1);
static int
button_open (struct inode *inode, struct file *fp)
{
unsigned int i;
// #define atomic_dec_and_test(v) (atomic_sub_return(1, (v)) == 0)
// !(--can_open == 0)
if (!atomic_dec_and_test(&can_open)) {
atomic_inc(&can_open); // ++can_open
return -EBUSY;
}
...
}
static int
button_close (struct inode *inode, struct file *fp)
{
atomic_inc(&can_open);
}
信号量
信号量也能实现互斥,并且当获得资源失败时,进程可以睡在该信号量上,等待被唤醒。
定义信号量
struct semaphore sem;
初始化信号量
void sema_init(struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem); // 初始值为0
static DECLARE_MUTEX(button_lock); // 定义互斥锁
获得信号量
void down(struct semaphore *sem); // 获得失败,则陷入 不可打断的休眠,直到其他进程 up信号量,或被 SIGKILL杀死
int down_interruptible(struct semaphore *sem); // 获得失败,则陷入可打断休眠,直到其他进程up信号量,获得被信号打断返回1,成功获得信号量返回0
if (down_interruptible(&soft->algolock))
return -ERESTARTSYS;
int down_trylock(struct semaphore *sem); // 获得失败,则返回 1,获得成功返回 0
/* try to get control of the write buffer */
if (down_trylock(&sd->sd_wbs)) {
/* somebody else has it now;
* if we're non-blocking, then exit...
*/
if (file->f_flags & O_NONBLOCK) {
return -EAGAIN;
}
/* ...or if we want to block, then do so here */
if (down_interruptible(&sd->sd_wbs)) {
/* something went wrong with wait */
return -ERESTARTSYS;
}
}
释放信号量
void up(struct semaphore *sem);
阻塞和非阻塞
应用程序在读操作时,f_ops->read 检查是否有数据,无则 wait_event_interruptible 将本进程加入 事件队列,
驱动模块需注册中断,当中断发生时,wake_up_interruptible 唤醒队列上所有进程
判断非阻塞
驱动程序可以获得 file->f_flags & O_NONBLOCK 以判断是否非阻塞处理
如果是非阻塞,并没有资源,则返回 -EAGAIN
按键消抖和定时器
由于机械按键的回弹导致刚按下时电压来回跳动,导致触发多次中断,所以应收到第一次中断设置一个定时器延迟处理中断,期间的其他中断忽略
定时器操作
// 定义定时器
static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0);
// 初始化定时器,但不会加入系统
init_timer(&button_timer);
// 设置定时器并加入系统
button_timer.expires = jiffies + msecs_to_jiffies(100); // 100ms 后读取按键
button_timer.data = (unsigned long)dev_id;
mod_timer(&button_timer, button_timer.expires);
// 从系统中删除定时器
del_timer(&button_timer);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2022-02-21 kernel——module