嵌入式Linux设备驱动开发——selec/poll

2019-07-13 00:40发布

应用程序调用select,select系统调用的原型:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); fd_set数据结构来表示要监听的设备,里面存放的是设备的文件描述符 nfds:当前进程最大的文件描述符+1 readfds:监听设备的读操作 writefds:监听设备的写操作 execptfds:监听设备的异常情况 timeout:超时处理,如果指定为NULL,select如果以上三种情况都没有发生,进行永远的休眠,如果指定超时时间,超时到期立即返回   功能:监听设备,前提是要将设备的文件描述符放置在fd_set文件描述符 集合中,如果三种情况都不满足,当前进程调用select进行休眠操作, 如果设备可用或者超时,select函数立即返回,执行后续代码   关于文件描述符结合fd_set的操作宏: void FD_CLR(int fd, fd_set *set); //将集合中的某一个设备清除 int  FD_ISSET(int fd, fd_set *set); //判断设备是否可用 void FD_SET(int fd, fd_set *set); //添加要监听的设备 void FD_ZERO(fd_set *set); //初始化fd_set   对应底层驱动的实现: 当应用程序调用select或者poll时,都会调用到底层驱动的同一个poll接口, 底层驱动的poll接口在struct file_operations中: unsigned int (*poll) (struct file *, struct poll_table_struct *); 这个底层poll的实现及其简单,都会调用poll_wait函数,然后判断 数据的可用性或者异常,根据此信息返回0或者非0值,这样就完成了 底层poll的实现。 通过分析大量内核其他的驱动代码,得到关于poll接口使用的特点: 1.首先驱动程序都会定义一个等待队列头 2.然后调用poll_wait(文件指针,等待队列头,设备列表),调用 这个函数的效果是让当前进程添加到这个队列头中,注意只是添加, 不会立即休眠 3.最后判断设备的状态,根据状态返回0或者非0   如果要深入了解poll和select的使用,需要了解内核的系统调用流程: fs/select.c app:select/poll->sys_select/sys_poll->drv_poll:   sys_poll:   do_sys_poll     do_poll     int timed_out = 0, count = 0;       for (;;) {         for(遍历所监听的设备)           if (do_pollfd(pfd, pt)) {           //根据fd找到对应的file,然后判断file->f_op(它指向的是           驱动的file_operations,)中是否有poll接口,如果有,           那么调用它,并且返回poll接口的返回值,说明变量count           的值有底层驱动poll的返回值决定             count++;             pt = NULL;           }                      //如果遍历设备,执行各个设备驱动的poll接口,都返回0           ,接下来就会判断当前进程是否接收到信号,如果接收到           信号count为非0值           if (!count) {             count = wait->error;             if (signal_pending(current))             count = -EINTR;           }                      //跳出死循环的条件是:count非0或者time_out非0(超时)           if (count || timed_out)             break;           //如果设备即不可用,有没有接收到信号,那么当前进程           进入休眠,如果指定了超时时间,超时时间到期,           立即返回0,timed_out=1,死循环再执行一次            if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))               timed_out = 1; }       } //如果调出死循环,就会立即返回到用户空间,也表示select       或者poll系统调用函数的返回   总结: 1.驱动的poll接口完成一下两个工作   1.调用poll_wait将当前进程添加到等待队列头中,注意每个驱动程序   都有对应的唯一的队列头,但里面添加的指定的进程只有应用程序对应的主进程   2.判断设备的状态,返回0或者非0,0表示设备不可用或者正常,菲0表示设备可用或者异常    2.系统调用过程完成:   1.调用底层驱动poll接口来判断数据的可用性或者异常情况   2.判断当前进程是否接收到信号,如果接收到信号,理解返回到用户空间   3.如果设备不可用或者没有接收到信号,进入真正的休眠      4.如果休眠,要返回用户空间的条件:     1.数据可用,底层驱动来判断数据可用,比如中断,在中断处理函数中唤醒休眠的进程     2.接收到信号     3.超时返回      案例:给第二版本的按键驱动程序添加poll的实现 案例:在测试程序中将此段代码放在while(1)前面,看看效果     FD_ZERO(&rdfd); FD_SET(fd, &rdfd); //监听按键 //FD_SET(fd2, &rdfd); 监听串口 tv.tv_sec = 5; //超时时间5秒 tv.tv_usec = 0; ______________________________________________________________________________________ #include #include #include #include #include #include #include #include #include #include #include #include #include #include   //定义驱动硬件私有结构体 struct button_hw_priv { char *name; unsigned long gpio; int irq; int code;  };   //定义平台私有结构 struct button_platdata { struct button_hw_priv *buttons; int nbuttons; };   //定义驱动私有结构体 struct button_sf_priv { struct cdev btn_cdev; int major; struct class *cls; };   //定义按键状态结构 struct button_event{ int code;  //键值 int state; //状态 };   static struct button_event btn_event; static wait_queue_head_t btn_wq; static int is_press;   //初始化按键信息 static struct button_hw_priv btn_info[] = { [0] = { .name = "KEY_UP", .gpio = S5PV210_GPH0(0), .irq = IRQ_EINT(0), .code = KEY_UP }, [1] = { .name = "KEY_DOWN", .gpio = S5PV210_GPH0(1), .irq = IRQ_EINT(1), .code = KEY_DOWN }, };   //初始化平台私有的硬件信息 static struct button_platdata button_data = { .buttons = btn_info, .nbuttons = ARRAY_SIZE(btn_info) };   static void button_release(struct device *dev) { }   //分配初始化platform_device static struct platform_device button_dev = { .name = "mybutton", .id = -1, .dev = { .release = button_release, .platform_data = &button_data } };   static int button_open(struct inode *inode,struct file *file) { struct button_sf_priv *pbtn = container_of(inode->i_cdev, struct button_sf_priv,btn_cdev); file->private_data = pbtn; return 0; }   static unsigned int button_poll(struct file *file,struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(file, &btn_wq, wait); //不会立即休眠 if(is_press) { mask |= POLLIN | POLLRDNORM;//设备可用,数据可用 } return mask; }   static ssize_t button_read(struct file *file,char __user *buf,size_t count,loff_t *ppos) { wait_event_interruptible(btn_wq, is_press != 0); is_press = 0; copy_to_user(buf, &btn_event, sizeof(btn_event)); return count; }   static struct file_operations button_fops = { .owner = THIS_MODULE, .open = button_open, .poll = button_poll, .read = button_read };   static irqreturn_t button_isr(int irq, void *dev_id) { struct button_hw_priv *pbtn_hw =  (struct button_hw_priv *)dev_id;   btn_event.state = !gpio_get_value(pbtn_hw->gpio); btn_event.code = pbtn_hw->code; wake_up_interruptible(&btn_wq); is_press = 1; return IRQ_HANDLED; }   static int button_probe(struct platform_device *pdev) { int i; dev_t dev_id; struct button_platdata *pbtndata =pdev->dev.platform_data; struct button_sf_priv *pbtn = kzalloc(sizeof(struct button_sf_priv), GFP_KERNEL); if (pbtn->major) { dev_id = MKDEV(pbtn->major, 0); register_chrdev_region(dev_id, 1, "button"); } else { alloc_chrdev_region(&dev_id, 0, 1, "button"); pbtn->major = MAJOR(dev_id); }   cdev_init(&pbtn->btn_cdev, &button_fops); cdev_add(&pbtn->btn_cdev, dev_id, 1); pbtn->cls = class_create(THIS_MODULE, "button"); device_create(pbtn->cls, NULL, dev_id, NULL, "button");   for(i = 0; i < pbtndata->nbuttons; i++) { struct button_hw_priv *pbtn_hw = NULL; pbtn_hw = &pbtndata->buttons[i]; gpio_request(pbtn_hw->gpio, pbtn_hw->name); request_irq(pbtn_hw->irq, button_isr,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,pbtn_hw->name, pbtn_hw); } init_waitqueue_head(&btn_wq);   dev_set_drvdata(&pdev->dev, pbtn); return 0; }   static int button_remove(struct platform_device *pdev) { int i; struct button_platdata *pbtndata = pdev->dev.platform_data; struct button_sf_priv *pbtn = dev_get_drvdata(&pdev->dev);   dev_t dev_id = MKDEV(pbtn->major, 0); for(i = 0; i < pbtndata->nbuttons; i++) { struct button_hw_priv *pbtn_hw = &pbtndata->buttons[i]; gpio_free(pbtn_hw->gpio); free_irq(pbtn_hw->irq, pbtn_hw); } device_destroy(pbtn->cls, dev_id); class_destroy(pbtn->cls); cdev_del(&pbtn->btn_cdev); unregister_chrdev_region(dev_id, 1);   kfree(pbtn); return 0; }   //分配初始化platform_driver static struct platform_driver button_drv = { .driver = { .name = "mybutton", .owner = THIS_MODULE }, .probe = button_probe, .remove = button_remove };   static int button_init(void) { //注册platform_device platform_device_register(&button_dev); //注册platform_driver platform_driver_register(&button_drv); return 0; }     static void button_exit(void) { platform_device_unregister(&button_dev); platform_driver_unregister(&button_drv); }   module_init(button_init); module_exit(button_exit); MODULE_LICENSE("GPL v2");   ______________________________________________________________________________________ 测试APP ______________________________________________________________________________________ #include #include #include #include #include #include #include #include   struct button_event { int code; int state; };   int main(int argc, char *argv[]) { struct button_event btn_event; int fd; fd_set rdfd; //读设备集合 struct timeval tv; int ret = 0; fd = open("/dev/button", O_RDWR); if (fd < 0) { printf("open button failed. "); return -1; }   while (1) { FD_ZERO(&rdfd); FD_SET(fd, &rdfd); //监听按键 //FD_SET(fd2, &rdfd); 监听串口 tv.tv_sec = 5; //超时时间5秒 tv.tv_usec = 0; //启动监听 ret = select(fd+1, &rdfd, NULL, NULL, &tv); if (ret < 0) printf("select error! "); else if (ret == 0) printf("timeout! "); else { if (FD_ISSET(fd, &rdfd)) { //判断数据是否来源按键 read(fd, &btn_event,  sizeof(btn_event)); printf("code = %d, state = %d ", btn_event.code, btn_event.state); } } }   close(fd); return 0; }