驱动:8.1设备的阻塞方式访问

本文详细介绍了一种基于Linux内核的按钮设备驱动程序的实现过程。从用户空间的读取操作到驱动程序中的等待队列机制,文章深入探讨了如何通过等待队列处理设备I/O就绪状态,以及如何在设备I/O未就绪时唤醒睡眠进程。通过具体代码示例,展示了信号量、按键键值缓冲区和等待队列头等关键数据结构的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用户空间:recv/accept
fd = open("/dev/mybuttons",…);
read(fd,buf,len);//有数据读,无数据等待
驱动程序:
要是实现设备的阻塞方式操作,要使用内核中的等待队列机制

等待队列的核心数据结构:

struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

使用步骤:

1)定义等待队列头变量
	wait_queue_head_t btn_wqh;
2)初始化等待队列头
	init_waitqueue_head(&btn_wqh);
	以上两步也可以使用DECLARE_WAIT_QUEUE_HEAD(btn_wqh);来替换
3)当设备I/O就需,可以调用以下函数进去睡眠状态
	wait_event_interruptible(btn_wqh,condition);//让调用进程进入深度睡眠
	wait_event_interruptible(btn_wqh,condition);//让调用者进程进入可中断的睡眠
		condition,该表达式为false时,对应的进程进入睡眠状态,true时,以上两个函数直接返回,不睡眠
4)当设备I/O未就绪时,可以调用以下两个函数完成睡眠进程的唤醒
	wake_up(&btn_wqh);
	wake_up_interruptible(&btn_wqh);

案例代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
struct class *cls;
/*定义一个信号量*/
struct semaphore btn_sem;
/*定义按键键值缓冲区*/
unsigned char key_buf;
/*表示按键缓冲区中是否有键值 0,无键值  1,有键值*/
volatile unsigned int ev_press = 0;
/*定义一个等待队列头*/
wait_queue_head_t btn_wqh;
int btn_open(struct inode *inode, struct file *filp)
{
    /*获取信号量*/
    //down(&btn_sem);
    if(down_interruptible(&btn_sem))
    {
        return -ERESTARTSYS;
    }
    return 0;
}
int btn_close(struct inode *inode, struct file *filp)
{
    up(&btn_sem);
    return 0;
}
ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
    int ret = 0;
    /*有数据就直接返回给用户空间 无数据就睡眠等待
     *等待过程中一旦有数据可供用户空间读,就唤醒睡眠进程
     *将数据返回到用户空间
     * */
    wait_event_interruptible(btn_wqh, ev_press);
    /*将键值拷贝到用户空间内存*/
    ret = copy_to_user(buf, &key_buf, len);
    ev_press = 0;
    return len;
}
struct file_operations btn_fops =
{
    .owner = THIS_MODULE,
    .open = btn_open,
    .release = btn_close, 
    .read   = btn_read, 
};
irqreturn_t btn_isr(int irq, void *dev)
{
    /*保存按键值*/
    key_buf = 0x10;
    ev_press = 1;//按键缓冲区中有键值
    /*唤醒因无键值而睡眠的进程*/
    wake_up_interruptible(&btn_wqh);
    return IRQ_HANDLED;
}
int __init btn_drv_init(void)
{
    int ret = 0;
    /*申请设备号*/
    alloc_chrdev_region(&dev, 0, 1, "mybuttons");
    /*初始化cdev*/
    cdev_init(&btn_cdev, &btn_fops);
    /*注册cdev*/
    cdev_add(&btn_cdev, dev, 1);
    /*创建设备文件*/
    cls = class_create(THIS_MODULE, "buttons");
    device_create(cls, NULL, dev, NULL, "mybuttons");
    /*初始化信号量*/
    sema_init(&btn_sem, 1);
    /*初始化等待队列头*/
    init_waitqueue_head(&btn_wqh);
    /*注册中断*/
    ret = request_irq(IRQ_GPIO_A_START+28, btn_isr, IRQF_TRIGGER_FALLING, "up", NULL);
    return 0;
}
void __exit btn_drv_exit(void)
{
    /*注销中断*/
    free_irq(IRQ_GPIO_A_START+28, NULL);
    /*销毁设备文件*/
    device_destroy(cls, dev);
    class_destroy(cls);
    /*注销cdev*/
    cdev_del(&btn_cdev);
    /*注销设备号*/
    unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);

实验:

  1)make
  2)arm-cortex_a9-linux-gnueabi-gcc test.c -o test
  3) cp ../../rootfs
  4) 在板子上执行
     insmod btn_drv.ko
     ./test   阻塞睡眠等待
     按一下按键 串口中打印了一个或者多个(抖动)键值
     test进程继续阻塞        

原理:

 wait_event_interruptible 
    __wait_event_interruptible{
       /*定义一个等待队列 (链表中的一个节点)
         该节点记录要阻塞睡眠的那个进程的相关信息
       */
       DEFINE_WAIT(__wait){
           /*定义链表节点*/
           wait_queue_t name = {						\
            /*保存要睡眠进程的相关信息
              current始终指向真正CPU中执行的那个进程
              内核中管理进程会为每个进程创建struct task_struct对象 
             */
			.private	= current,				\
			.func		= function,				\
			.task_list	= LIST_HEAD_INIT((name).task_list),	\
		  }
       }
       
       prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE){
          /*将定义好的节点,插入到等待队列头指向的链表中去*/
          __add_wait_queue(q, wait);
          /*将当前进程状态设置TASK_INTERRUPTIBLE(可中断的睡眠状态)*/
          set_current_state(state);
        }
       schedule();//内核中任务调度器
       }
    }           
   内核中管理字符设备会为其创建struct cdev类型对象
   内核中管理进程会为其创建struct task_struct类型对象
     struct task_struct
     {
        /*保存进程的当前状态*/
        volatile long state;
        ...
     }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值