一,、驅(qū)動實現(xiàn)select機制的步驟 1,、首先初始化一個等待隊列頭 2,、在驅(qū)動中實現(xiàn)poll函數(shù),,該函數(shù)只需做兩件事情 a,、使用poll_wait()函數(shù)將等待隊列添加到poll_table中,。 b,、返回描述設(shè)備是否可讀或可寫的掩碼,。 3,、在驅(qū)動的相應(yīng)地方調(diào)用wake_up()函數(shù),,喚醒等待隊列。 兩點說明: a,、等待隊列 select函數(shù)阻塞的原理,,實際上是通過等待隊列實現(xiàn)的,若對等待隊列不熟悉,,請看我的另一篇文章《等待隊列的簡單使用》,。否則看以下的 “select機制內(nèi)核代碼走讀” 會很吃力。 b,、掩碼值及含義 POLLIN 如果設(shè)備可被不阻塞地讀, 這個位必須設(shè)置. POLLRDNORM 這個位必須設(shè)置, 如果"正常"數(shù)據(jù)可用來讀. 一個可讀的設(shè)備返回( POLLIN|POLLRDNORM ). POLLRDBAND 這個位指示帶外數(shù)據(jù)可用來從設(shè)備中讀取. 當(dāng)前只用在 Linux 內(nèi)核的一個地方( DECnet 代碼 )并且通常對設(shè)備驅(qū)動不可用. POLLPRI 高優(yōu)先級數(shù)據(jù)(帶外)可不阻塞地讀取. 這個位使 select 報告在文件上遇到一個異常情況, 因為 selct 報告帶外數(shù)據(jù)作為一個異常情況. POLLHUP 當(dāng)讀這個設(shè)備的進(jìn)程見到文件尾, 驅(qū)動必須設(shè)置 POLLUP(hang-up). 一個調(diào)用 select 的進(jìn)程被告知設(shè)備是可讀的, 如同 selcet 功能所規(guī)定的. POLLERR 一個錯誤情況已在設(shè)備上發(fā)生. 當(dāng)調(diào)用 poll, 設(shè)備被報告位可讀可寫, 因為讀寫都返回一個錯誤碼而不阻塞. POLLOUT 這個位在返回值中設(shè)置, 如果設(shè)備可被寫入而不阻塞. POLLWRNORM 這個位和 POLLOUT 有相同的含義, 并且有時它確實是相同的數(shù). 一個可寫的設(shè)備返回( POLLOUT|POLLWRNORM). POLLWRBAND 如同 POLLRDBAND , 這個位意思是帶有零優(yōu)先級的數(shù)據(jù)可寫入設(shè)備. 只有 poll 的數(shù)據(jù)報實現(xiàn)使用這個位, 因為一個數(shù)據(jù)報看傳送帶外數(shù)據(jù). 注:應(yīng)當(dāng)重復(fù)一下 POLLRDBAND 和 POLLWRBAND 僅僅對關(guān)聯(lián)到 socket 的文件描述符有意義: 通常設(shè)備驅(qū)動不使用這些標(biāo)志,! 二、以按鍵驅(qū)動為例 驅(qū)動代碼button.c #include <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/device.h> #include <linux/init.h> #include <linux/major.h> #include <linux/delay.h> #include <linux/io.h> #include <asm/uaccess.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/gpio.h> #include <asm/gpio.h> #define BUTTON_NAME "poll_button" #define BUTTON_GPIO 140 static int button_major = 0; static int button_minor = 0; static struct cdev button_cdev; static struct class *p_button_class = NULL; static struct device *p_button_device = NULL; static struct timer_list button_timer; static volatile int ev_press = 0; static volatile char key_value[] = {0}; static int old_value; static int Button_Irq = 0; static int flag_interrupt = 1; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static irqreturn_t buttons_interrupt(int irq, void *dev_id) { if(flag_interrupt) { flag_interrupt = 0; old_value = gpio_get_value(BUTTON_GPIO); mod_timer(&button_timer,jiffies + HZ/100); //啟動消抖定時器,,消抖時間10ms } return IRQ_RETVAL(IRQ_HANDLED); } static void button_timer_handle(unsigned long arg) { int tmp_value; tmp_value = gpio_get_value(BUTTON_GPIO); if(tmp_value == old_value) { key_value[0] = tmp_value; ev_press= 1; //有按鍵按下,,喚醒等待隊列 wake_up_interruptible(&button_waitq); } flag_interrupt = 1; } static int button_open(struct inode *inode,struct file *file) { Button_Irq = gpio_to_irq(BUTTON_GPIO); enable_irq(Button_Irq); if(request_irq(Button_Irq, buttons_interrupt, IRQF_TRIGGER_FALLING, "BUTTON_IRQ", NULL) != 0) { printk("request irq failed !!! \n"); disable_irq(Button_Irq); free_irq(Button_Irq, NULL); return -EBUSY; } return 0; } static int button_close(struct inode *inode, struct file *file) { free_irq(Button_Irq, NULL); return 0; } static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (filp->f_flags & O_NONBLOCK) { /*nothing to do*/ //如果沒有使用select機制,,并且應(yīng)用程序設(shè)置了非阻塞O_NONBLOCK,那么驅(qū)動這里就不使用等待隊列進(jìn)行等待,。 } else { wait_event_interruptible(button_waitq, ev_press); //如果應(yīng)用層沒有使用select,,直接讀的話,這里會阻塞,,直到按鍵按下,。如果使用select機制,進(jìn)來這里時ev_press為真,,不會阻塞,。 } err = copy_to_user(buff, (const void *)key_value, min(sizeof(key_value), count)); key_value[0] = 0; ev_press = 0; return err ? -EFAULT : min(sizeof(key_value), count); } static unsigned int button_poll(struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; //將等待隊列添加到poll_table中 poll_wait(file, &button_waitq, wait); if(ev_press) { //返回描述設(shè)備是否可讀或可寫的掩碼 mask = POLLIN | POLLRDNORM; } return mask; } static const struct file_operations button_fops = { .owner = THIS_MODULE, .open = button_open, .release = button_close, .read = button_read, .poll = button_poll, //.write = button_write, //.ioctl = button_ioctl }; static int button_setup_cdev(struct cdev *cdev, dev_t devno) { int ret = 0; cdev_init(cdev, &button_fops); cdev->owner = THIS_MODULE; ret = cdev_add(cdev, devno, 1); return ret; } static int __init button_init(void) { int ret; dev_t devno; printk("button driver init...\n"); init_timer(&button_timer); button_timer.function = &button_timer_handle; if(button_major) { devno = MKDEV(button_major, button_minor); ret = register_chrdev_region(devno, 1, BUTTON_NAME); } else { ret = alloc_chrdev_region(&devno, button_minor, 1, BUTTON_NAME); button_major = MAJOR(devno); } if(ret < 0) { printk("get button major failed\n"); return ret; } ret = button_setup_cdev(&button_cdev, devno); if(ret) { printk("button setup cdev failed, ret = %d\n",ret); goto cdev_add_fail; } p_button_class = class_create(THIS_MODULE, BUTTON_NAME); ret = IS_ERR(p_button_class); if(ret) { printk(KERN_WARNING "button class create failed\n"); goto class_create_fail; } p_button_device = device_create(p_button_class, NULL, devno, NULL, BUTTON_NAME); ret = IS_ERR(p_button_device); if (ret) { printk(KERN_WARNING "button device create failed, error code %ld", PTR_ERR(p_button_device)); goto device_create_fail; } return 0; device_create_fail: class_destroy(p_button_class); class_create_fail: cdev_del(&button_cdev); cdev_add_fail: unregister_chrdev_region(devno, 1); return ret; } static void __exit button_exit(void) { dev_t devno; printk("button driver exit...\n"); del_timer_sync(&button_timer); devno = MKDEV(button_major, button_minor); device_destroy(p_button_class, devno); class_destroy(p_button_class); cdev_del(&button_cdev); unregister_chrdev_region(devno, 1); } module_init(button_init); module_exit(button_exit); MODULE_AUTHOR("Jimmy"); MODULE_DESCRIPTION("button Driver"); MODULE_LICENSE("GPL"); 驅(qū)動Makefile文件 ifneq ($(KERNELRELEASE),) obj-m := button.o else KERNELDIR ?= /ljm/git_imx6/linux-fsl/src/linux-3-14-28-r0 TARGET_CROSS = arm-none-linux-gnueabi- PWD := $(shell pwd) default: $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules endif install: $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order 應(yīng)用程序main.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/ioctl.h> #define DEV_BUTTON "/dev/poll_button" int main(void) { int dev_fd; int ret; char read_buf[20] = {-1}; struct timeval rto; fd_set read_fds; rto.tv_sec = 10; rto.tv_usec = 0; dev_fd = open(DEV_BUTTON, O_RDWR /*| O_NONBLOCK*/); if ( dev_fd == -1 ) { printf("open %s failed, ret = %d\n", DEV_BUTTON, dev_fd); return -1; } while(1) { rto.tv_sec =10; rto.tv_usec = 0; FD_ZERO(&read_fds); FD_SET(dev_fd, &read_fds); ret = select(dev_fd+1, &read_fds, NULL, NULL, &rto); if(ret == -1) { printf("error\n"); continue; } else if(ret == 0) { printf("timeout\n"); continue; } else { if(FD_ISSET(dev_fd, &read_fds)) { read(dev_fd, read_buf, 1); printf("button pressed, val = %d\n", read_buf[0]); } } } printf("clsoe %s\n", DEV_BUTTON); close(dev_fd); return 0; } 應(yīng)用程序Makefile WORKDIR = INCLUDES = -I. LIBS = LINKS = -lpthread CC = arm-none-linux-gnueabi-gcc TARGET = main src=$(wildcard *.c ./callback/*.c) C_OBJS=$(patsubst %.c, %.o,$(src)) #C_OBJS=$(dir:%.c=%.o) compile:$(TARGET) $(C_OBJS):%.o:%.c $(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c $(TARGET):$(C_OBJS) $(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) @echo @echo Project has been successfully compiled. @echo install: $(TARGET) cp $(TARGET) $(INSTALL_PATH) uninstall: rm -f $(INSTALL_PATH)/$(TARGET) rebuild: clean compile clean: rm -rf *.o $(TARGET) *.log *~ 三、select的整體流程 應(yīng)用層的select函數(shù)會調(diào)用到內(nèi)核函數(shù)do_select,,do_select調(diào)用驅(qū)動的poll函數(shù),,若poll函數(shù)返回的掩碼不可讀寫,那么do_select進(jìn)入睡眠阻塞,。要從睡眠中醒來并且跳出,,有兩種情況:a、超時跳出,;b,、驅(qū)動中喚醒等待隊列,這時do_select再次調(diào)用poll函數(shù),,如果poll函數(shù)返回的掩碼可讀寫,,那么就跳出阻塞,否則繼續(xù)睡眠,。注意:上述是在select函數(shù)設(shè)成阻塞的情況,,select函數(shù)可以設(shè)置成非阻塞的(將select函數(shù)的timeout參數(shù)設(shè)置成0)。 四,、select機制內(nèi)核代碼走讀 調(diào)用順序如下select() -> core_sys_select() -> do_select() -> fop->poll() 1,、select函數(shù)解析 <pre name="code">SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp, fd_set __user *, exp, struct timeval __user *, tvp) { struct timespec end_time, *to = NULL; struct timeval tv; int ret; if (tvp) {// 如果超時值非NULL if (copy_from_user(&tv, tvp, sizeof(tv))) // 從用戶空間取數(shù)據(jù)到內(nèi)核空間 return -EFAULT; to = &end_time; // 得到timespec格式的未來超時時間 if (poll_select_set_timeout(to, tv.tv_sec + (tv.tv_usec / USEC_PER_SEC), (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) return -EINVAL; } ret = core_sys_select(n, inp, outp, exp, to); // 關(guān)鍵函數(shù) ret = poll_select_copy_remaining(&end_time, tvp, 1, ret); /*如果有超時值, 并拷貝離超時時刻還剩的時間到用戶空間的timeval中*/ return ret; // 返回就緒的文件描述符的個數(shù) } 2、core_sys_select函數(shù)解析 int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec *end_time) { fd_set_bits fds; /** typedef struct { unsigned long *in, *out, *ex; unsigned long *res_in, *res_out, *res_ex; } fd_set_bits; 這個結(jié)構(gòu)體中定義的全是指針,,這些指針都是用來指向描述符集合的。 **/ void *bits; int ret, max_fds; unsigned int size; struct fdtable *fdt; /* Allocate small arguments on the stack to save memory and be faster */ long stack_fds[SELECT_STACK_ALLOC/sizeof(long)]; // 256/32 = 8, stack中分配的空間 /** @ include/linux/poll.h #define FRONTEND_STACK_ALLOC 256 #define SELECT_STACK_ALLOC FRONTEND_STACK_ALLOC **/ ret = -EINVAL; if (n < 0) goto out_nofds; /* max_fds can increase, so grab it once to avoid race */ rcu_read_lock(); fdt = files_fdtable(current->files); // RCU ref, 獲取當(dāng)前進(jìn)程的文件描述符表 max_fds = fdt->max_fds; rcu_read_unlock(); if (n > max_fds) // 如果傳入的n大于當(dāng)前進(jìn)程最大的文件描述符,,給予修正 n = max_fds; /* * We need 6 bitmaps (in/out/ex for both incoming and outgoing), * since we used fdset we need to allocate memory in units of * long-words. */ size = FDS_BYTES(n); // 以一個文件描述符占一bit來計算,,傳遞進(jìn)來的這些fd_set需要用掉多少個字 bits = stack_fds; if (size > sizeof(stack_fds) / 6) { // 除6,為什么?因為每個文件描述符需要6個bitmaps /* Not enough space in on-stack array; must use kmalloc */ ret = -ENOMEM; bits = kmalloc(6 * size, GFP_KERNEL); // stack中分配的太小,,直接kmalloc if (!bits) goto out_nofds; } // 這里就可以明顯看出struct fd_set_bits結(jié)構(gòu)體的用處了,。 fds.in = bits; fds.out = bits + size; fds.ex = bits + 2*size; fds.res_in = bits + 3*size; fds.res_out = bits + 4*size; fds.res_ex = bits + 5*size; // get_fd_set僅僅調(diào)用copy_from_user從用戶空間拷貝了fd_set if ((ret = get_fd_set(n, inp, fds.in)) || (ret = get_fd_set(n, outp, fds.out)) || (ret = get_fd_set(n, exp, fds.ex))) goto out; zero_fd_set(n, fds.res_in); // 對這些存放返回狀態(tài)的字段清0 zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_ex); ret = do_select(n, &fds, end_time); // 關(guān)鍵函數(shù),完成主要的工作 if (ret < 0) // 有錯誤 goto out; if (!ret) { // 超時返回,,無設(shè)備就緒 ret = -ERESTARTNOHAND; if (signal_pending(current)) goto out; ret = 0; } // 把結(jié)果集,拷貝回用戶空間 if (set_fd_set(n, inp, fds.res_in) || set_fd_set(n, outp, fds.res_out) || set_fd_set(n, exp, fds.res_ex)) ret = -EFAULT; out: if (bits != stack_fds) kfree(bits); // 如果有申請空間,,那么釋放fds對應(yīng)的空間 out_nofds: return ret; // 返回就緒的文件描述符的個數(shù) } 3、do_select函數(shù)解析 int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; poll_table *wait; int retval, i, timed_out = 0; unsigned long slack = 0; rcu_read_lock(); // 根據(jù)已經(jīng)設(shè)置好的fd位圖檢查用戶打開的fd, 要求對應(yīng)fd必須打開, 并且返回 // 最大的fd。 retval = max_select_fd(n, fds); rcu_read_unlock(); if (retval < 0) return retval; n = retval; // 一些重要的初始化: // poll_wqueues.poll_table.qproc函數(shù)指針初始化,,該函數(shù)是驅(qū)動程序中poll函數(shù)實 // 現(xiàn)中必須要調(diào)用的poll_wait()中使用的函數(shù),。 poll_initwait(&table); wait = &table.pt; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; // 如果系統(tǒng)調(diào)用帶進(jìn)來的超時時間為0,那么設(shè)置 // timed_out = 1,,表示不阻塞,,直接返回。 } if (end_time && !timed_out) slack = estimate_accuracy(end_time); // 超時時間轉(zhuǎn)換 retval = 0; for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; inp = fds->in; outp = fds->out; exp = fds->ex; rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex; // 所有n個fd的循環(huán) for (i = 0; i < n; ++rinp, ++routp, ++rexp) { unsigned long in, out, ex, all_bits, bit = 1, mask, j; unsigned long res_in = 0, res_out = 0, res_ex = 0; const struct file_operations *f_op = NULL; struct file *file = NULL; // 先取出當(dāng)前循環(huán)周期中的32個文件描述符對應(yīng)的bitmaps in = *inp++; out = *outp++; ex = *exp++; all_bits = in | out | ex; // 組合一下,,有的fd可能只監(jiān)測讀,,或者寫,或者e rr,,或者同時都監(jiān)測 if (all_bits == 0) { // 這32個描述符沒有任何狀態(tài)被監(jiān)測,,就跳入下一個32個fd的循環(huán)中 i += __NFDBITS; //每32個文件描述符一個循環(huán),正好一個long型數(shù) continue; } // 本次32個fd的循環(huán)中有需要監(jiān)測的狀態(tài)存在 for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {// 初始bit = 1 int fput_needed; if (i >= n) // i用來檢測是否超出了最大待監(jiān)測的fd break; if (!(bit & all_bits)) continue; // bit每次循環(huán)后左移一位的作用在這里,,用來跳過沒有狀態(tài)監(jiān)測的fd file = fget_light(i, &fput_needed); // 得到file結(jié)構(gòu)指針,,并增加引用計數(shù)字段f_count if (file) { // 如果file存在 f_op = file->f_op; mask = DEFAULT_POLLMASK; if (f_op && f_op->poll) { wait_key_set(wait, in, out, bit);// 設(shè)置當(dāng)前fd待監(jiān)測的事件掩碼 mask = (*f_op->poll)(file, wait); /* 調(diào)用驅(qū)動程序中的poll函數(shù),以evdev驅(qū)動中的 evdev_poll()為例該函數(shù)會調(diào)用函數(shù)poll_wait(file, &evdev->wait, wait),, 繼續(xù)調(diào)用__pollwait()回調(diào)來分配一個poll_table_entry結(jié)構(gòu)體,,該結(jié)構(gòu)體有一個內(nèi)嵌的等待隊列項, 設(shè)置好wake時調(diào)用的回調(diào)函數(shù)后將其添加到驅(qū)動程序中的等待隊列頭中,。 */ } fput_light(file, fput_needed); // 釋放file結(jié)構(gòu)指針,,實際就是減小他的一個引用計數(shù)字段f_count。 // mask是每一個fop->poll()程序返回的設(shè)備狀態(tài)掩碼,。 if ((mask & POLLIN_SET) && (in & bit)) { res_in |= bit; // fd對應(yīng)的設(shè)備可讀 retval++; wait = NULL; // 后續(xù)有用,,避免重復(fù)執(zhí)行__pollwait() } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; // fd對應(yīng)的設(shè)備可寫 retval++; wait = NULL; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; wait = NULL; } } } // 根據(jù)poll的結(jié)果寫回到輸出位圖里,返回給上級函數(shù) if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; /* 這里的目的純粹是為了增加一個搶占點。 在支持搶占式調(diào)度的內(nèi)核中(定義了CONFIG_PREEMPT),, cond_resched是空操作,。 */ cond_resched(); } wait = NULL; // 后續(xù)有用,避免重復(fù)執(zhí)行__pollwait() if (retval || timed_out || signal_pending(current)) break; if (table.error) { retval = table.error; break; } /*跳出這個大循環(huán)的條件有: 有設(shè)備就緒或有異常(retval!=0), 超時(timed_out = 1), 或者有中止信號出現(xiàn)*/ /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } // 第一次循環(huán)中,,當(dāng)前用戶進(jìn)程從這里進(jìn)入休眠,, // 上面?zhèn)飨聛淼某瑫r時間只是為了用在睡眠超時這里而已 // 超時,poll_schedule_timeout()返回0,;被喚醒時返回-EINTR if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; /* 超時后,,將其設(shè)置成1,方便后面退出循環(huán)返回到上層 */ } // 清理各個驅(qū)動程序的等待隊列頭,,同時釋放掉所有空出來的page頁(poll_table_entry) poll_freewait(&table); return retval; // 返回就緒的文件描述符的個數(shù) } --------------------- 作者:o倚樓聽風(fēng)雨o 來源:CSDN 原文:https://blog.csdn.net/silent123go/article/details/52577648 版權(quán)聲明:本文為博主原創(chuàng)文章,,轉(zhuǎn)載請附上博文鏈接! |
|