驱动实现select机制的步骤

一、驱动实现select机制的步骤

1、首先初始化一个等待队列头

2、在驱动中实现poll函数,该函数只需做两件事情

a、使用poll_wait()函数将等待队列添加到poll_table中。

b、返回描述设备是否可读或可写的掩码。

3、在驱动的相应地方调用wake_up()函数,唤醒等待队列。

两点说明:

a、等待队列

select函数阻塞的原理,实际上是通过等待队列实现的,若对等待队列不熟悉,请看我的另一篇文章《等待队列的简单使用》。否则看以下的 “select机制内核代码走读” 会很吃力。

b、掩码值及含义

POLLIN

如果设备可被不阻塞地读, 这个位必须设置.

POLLRDNORM

这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).

POLLRDBAND

这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.

POLLPRI

高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.

POLLHUP

当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.

POLLERR

一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.

POLLOUT

这个位在返回值中设置, 如果设备可被写入而不阻塞.

POLLWRNORM

这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).

POLLWRBAND

如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.

注:应当重复一下 POLLRDBAND 和 POLLWRBAND 仅仅对关联到 socket 的文件描述符有意义: 通常设备驱动不使用这些标志!

二、以按键驱动为例

驱动代码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机制,并且应用程序设置了非阻塞O_NONBLOCK,那么驱动这里就不使用等待队列进行等待。

} else {

wait_event_interruptible(button_waitq, ev_press); //如果应用层没有使用select,直接读的话,这里会阻塞,直到按键按下。如果使用select机制,进来这里时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) {

//返回描述设备是否可读或可写的掩码

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");

驱动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

应用程序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;

}

应用程序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的整体流程

应用层的select函数会调用到内核函数do_select,do_select调用驱动的poll函数,若poll函数返回的掩码不可读写,那么do_select进入睡眠阻塞。要从睡眠中醒来并且跳出,有两种情况:a、超时跳出;b、驱动中唤醒等待队列,这时do_select再次调用poll函数,如果poll函数返回的掩码可读写,那么就跳出阻塞,否则继续睡眠。注意:上述是在select函数设成阻塞的情况,select函数可以设置成非阻塞的(将select函数的timeout参数设置成0)。

四、select机制内核代码走读

调用顺序如下select() -> core_sys_select() -> do_select() -> fop->poll()

1、select函数解析

<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)))   // 从用户空间取数据到内核空间

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);             // 关键函数

ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);

/*如果有超时值, 并拷贝离超时时刻还剩的时间到用户空间的timeval中*/

return ret;             // 返回就绪的文件描述符的个数

}

2、core_sys_select函数解析

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;

这个结构体中定义的全是指针,这些指针都是用来指向描述符集合的。

**/

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, 获取当前进程的文件描述符表

max_fds = fdt->max_fds;

rcu_read_unlock();

if (n > max_fds)                     // 如果传入的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来计算,传递进来的这些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结构体的用处了。

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仅仅调用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);  // 对这些存放返回状态的字段清0

zero_fd_set(n, fds.res_out);

zero_fd_set(n, fds.res_ex);

ret = do_select(n, &fds, end_time);    // 关键函数,完成主要的工作

if (ret < 0)                           // 有错误

goto out;

if (!ret) {                            // 超时返回,无设备就绪

ret = -ERESTARTNOHAND;

if (signal_pending(current))

goto out;

ret = 0;

}

// 把结果集,拷贝回用户空间

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对应的空间

out_nofds:

return ret;                    // 返回就绪的文件描述符的个数

}

3、do_select函数解析

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();

// 根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回

// 最大的fd。

retval = max_select_fd(n, fds);

rcu_read_unlock();

if (retval < 0)

return retval;

n = retval;

// 一些重要的初始化:

// poll_wqueues.poll_table.qproc函数指针初始化,该函数是驱动程序中poll函数实

// 现中必须要调用的poll_wait()中使用的函数。

poll_initwait(&table);

wait = &table.pt;

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {

wait = NULL;

timed_out = 1;     // 如果系统调用带进来的超时时间为0,那么设置

// timed_out = 1,表示不阻塞,直接返回。

}

if (end_time && !timed_out)

slack = estimate_accuracy(end_time); // 超时时间转换

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的循环

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;

// 先取出当前循环周期中的32个文件描述符对应的bitmaps

in = *inp++; out = *outp++; ex = *exp++;

all_bits = in | out | ex;  // 组合一下,有的fd可能只监测读,或者写,或者e rr,或者同时都监测

if (all_bits == 0) {  // 这32个描述符没有任何状态被监测,就跳入下一个32个fd的循环中

i += __NFDBITS; //每32个文件描述符一个循环,正好一个long型数

continue;

}

// 本次32个fd的循环中有需要监测的状态存在

for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {// 初始bit = 1

int fput_needed;

if (i >= n)      // i用来检测是否超出了最大待监测的fd

break;

if (!(bit & all_bits))

continue; // bit每次循环后左移一位的作用在这里,用来跳过没有状态监测的fd

file = fget_light(i, &fput_needed); // 得到file结构指针,并增加引用计数字段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);// 设置当前fd待监测的事件掩码

mask = (*f_op->poll)(file, wait);

/*

调用驱动程序中的poll函数,以evdev驱动中的

evdev_poll()为例该函数会调用函数poll_wait(file, &evdev->wait, wait),

继续调用__pollwait()回调来分配一个poll_table_entry结构体,该结构体有一个内嵌的等待队列项,

设置好wake时调用的回调函数后将其添加到驱动程序中的等待队列头中。

*/

}

fput_light(file, fput_needed);

// 释放file结构指针,实际就是减小他的一个引用计数字段f_count。

// mask是每一个fop->poll()程序返回的设备状态掩码。

if ((mask & POLLIN_SET) && (in & bit)) {

res_in |= bit;         // fd对应的设备可读

retval++;

wait = NULL;       // 后续有用,避免重复执行__pollwait()

}

if ((mask & POLLOUT_SET) && (out & bit)) {

res_out |= bit;              // fd对应的设备可写

retval++;

wait = NULL;

}

if ((mask & POLLEX_SET) && (ex & bit)) {

res_ex |= bit;

retval++;

wait = NULL;

}

}

}

// 根据poll的结果写回到输出位图里,返回给上级函数

if (res_in)

*rinp = res_in;

if (res_out)

*routp = res_out;

if (res_ex)

*rexp = res_ex;

/*

这里的目的纯粹是为了增加一个抢占点。

在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),

cond_resched是空操作。

*/

cond_resched();

}

wait = NULL;  // 后续有用,避免重复执行__pollwait()

if (retval || timed_out || signal_pending(current))

break;

if (table.error) {

retval = table.error;

break;

}

/*跳出这个大循环的条件有: 有设备就绪或有异常(retval!=0), 超时(timed_out

= 1), 或者有中止信号出现*/

/*

* 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;

}

// 第一次循环中,当前用户进程从这里进入休眠,

// 上面传下来的超时时间只是为了用在睡眠超时这里而已

// 超时,poll_schedule_timeout()返回0;被唤醒时返回-EINTR

if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,

to, slack))

timed_out = 1; /* 超时后,将其设置成1,方便后面退出循环返回到上层 */

}

// 清理各个驱动程序的等待队列头,同时释放掉所有空出来的page页(poll_table_entry)

poll_freewait(&table);

return retval; // 返回就绪的文件描述符的个数

}

---------------------

作者:o倚楼听风雨o

来源:CSDN

原文:https://blog.csdn.net/silent123go/article/details/52577648

版权声明:本文为博主原创文章,转载请附上博文链接!

(0)

相关推荐