Linux设备驱动——字符设备的高阶驱动实现及关键要素之阻塞型I/O用法详解(休眠与唤醒机制)

阻塞型I/O

指当进程/线程执行某个需求时,因为当下无法立即满足请求,而使其进程或线程阻塞。

优点:在某些情况下,阻塞型 I/O 可以更有效地利用系统资源。在一些高负载场景下,可以避免频繁的上下文切换,降低系统开销。 而且阻塞型I/O的编程模型通常比较直观和易于理解。代码顺序执行(算作一个同步过程),不需要太多复杂的逻辑来处理异步操作和事件回调。

 开发经验:(驱动程序应该(默认)阻塞型进程,将其置入休眠状态直到请求可继续)

休眠唤醒机制

休眠“sleep” 指当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走。休眠中的进程会被搁置在一边,等待将来的某个事件发生。

开发的谨记:进程上下文(可休眠) 和 原子上下文(中断上下文)不可以进入休眠。驱动程序不能在拥有自旋锁、seqlock或者RCU锁时休眠。如果已经禁止了中断,也不能休眠。任何拥有信号量而休眠的代码必须很短,并且还要确保拥有信号量并不会阻塞最终会唤醒其自己的那个进程。完成唤醒任务的代码必须能够找到其休眠进程。

能够找到休眠的进程意味着,需要维护一个称为等待队列的数据结构。

等待队列本质是一个进程链表,通过一个“等待队列头”来管理。定义在<linux/wait.h>

//静态定义并初始化一个等待队列头
DECLARE_WAIT_QUEUE_HEAD(my_queue);

//使用动态方法
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);

使用wait_event宏,在实现休眠的同时,它也检查进程等待的条件

wait_event(queue, condition);
//如果进程在等待期间接收到中断信号,将返回-ERESTARTSYS
wait_event_interruptible(queue, condition);
//等待指定的条件满足,或者等待超时。超时单位是jiffies
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);

开发经验:最好的选择是使用wait_event_interruptible,它可以被信号中断。

该机制的另一部分就是唤醒,其他的某个执行线程(可能是另一个进程或者中断处理例程)

void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

wake_up会唤醒等待在给定queue上的所有进程,而wake_up_interruptible只会唤醒那些执行可中断休眠的进程。

显式的非阻塞IO由filp->f_flags中的O_NONBLOCK标志决定,定义在<linux/fcntl.h>。这个标志在默认时要被清除,因为等待数据的进程一般只是休眠。

注意:如果一个进程调用了read但是还没有数据数据可读,此进程必须阻塞。如果一个进程调用了write但缓冲区没有空间,此进程必须阻塞,而且必须休眠在与读取进程不同的等待队列上。

非阻塞型操作会立即返回,使得应用程序可以查询数据(查询方式),应用程序调用stdio函数必须非常小心,因为很容易把一个非阻塞返回错认为是EOF,所以必须始终检查errno。

底层的休眠

如何休眠

第一步通常是分配并初始化一个wait_queue_t结构,然后将其加入到对应的等待队列。

第二步是设置进程的状态,将其标志为休眠。

TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE

(通常不需要驱动程序代码来直接操作进程状态)如果需要,则可以调用函数如下

<linux/sched.h>

void set_current_state(int new_state);

亲自手动休眠

第一步建立并初始化一个等待队列入口,通常通过下面的宏完成:

DEFINE_WAIT(my_wait);

也可以这样定义

wait_queue_t my_wait;
init_wait(&my_wait);

第二步将等待队列入口添加到队列中,并设置进程的状态

void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);

第三步,进程即可调用schedule (此函数调用将调用调度器)

第四步,schedule 返回后,需要清理释放

void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

(其中queue和wait分别是等待队列头进程入口)

在调度器的调度算法中,使用完全公平调度算法(Completely Fair Scheduler, CFS),即使用红黑树来维护就绪进程队列,实现高效的进程选择和切换

独占休眠

可以预知只会有一个被唤醒并且获得资源的进程可以得到执行权,而其他被唤醒的进程只会再次休眠。这些被唤醒进程中的每一个都要获得处理器,为资源(以及锁)竞争,疯狂的“兽群行为”可能影响系统性能。因此独占休眠被引入

等待队列入口设置了WQ_FLAG_EXCLUSIVE标志时,则会被添加到等待队列的尾部。没有这个标志的入口会被添加到头部。

在某个等待队列上调用wake_up时,它会在唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后停止唤醒其他进程。

执行了独占休眠的进程,每次只会被唤醒其中一个,但是内核每次仍然会唤醒所有非独占休眠的进程。

注意:当满足,对某个资源存在严重竞争,并且唤醒单个进程就能完整消耗该资源时,利用独占休眠是值得的。

void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);

注意:wait_event及其变种无法执行独占休眠

唤醒细节

将进程设置为可运行状态,并且如果该进程具有更高优先级,则会执行一次上下文切换以便切换到该进程。

下面列出一些极少使用的变种函数

(wake_up会唤醒队列上所有非独占休眠的进程,以及单个独占休眠者(如果存在))

(wake_up_interruptible完成相同的工作,只是它会跳过不可中断休眠的那些进程)

wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);

 (它们特殊在会唤醒nr个独占休眠进程,而不是只有1个,这里如果填0,表示请求唤醒所有的独占休眠进程,而不是不唤醒任何一个)

wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);

(它们不管进程是否执行独占休眠,均唤醒它们(尽管中断形式仍然会跳过执行非中断休眠的进程))

wake_up_interruptible_sync(wait_queue_head_t *queue);

 (通常,被唤醒的进程可能会抢占当前的进程,并在wake_up返回前被调度到处理器上,如果不希望在这时被调度出处理器,则可使用这个sync(同步)变种,经常在打算强制重新调度的情况下使用,并且在只有很少的工作需要首先完成时更加有效)

阻塞型按键中断驱动

使用 NXP 公司 的IMX6ULL开发板 

采用总线设备驱动设备树的驱动框架进行,采用IO中断GPIO子系统的方式来设计

.dts (编译设备树 生成的.dtb文件需要放置在ARM开发板的/boot目录  并重启)

#在内核根目录编译设备树文件
make dtbs

 在根设备、iomuxc、iomuxc_snvs 下添加设备节点和pinctrl属性 及失能默认引脚

/ {
    model = "Freescale i.MX6 ULL 14x14 EVK Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

     gpio_keys_imx6ull {
        compatible = "my,gpio_key";
		gpios = <&gpio5 1 GPIO_ACTIVE_LOW
		 &gpio4 14 GPIO_ACTIVE_LOW>;
		
        pinctrl-names = "default";
        pinctrl-0 = <&key1_imx6ull &key2_imx6ull>;
    };

    gpio-keys {
        compatible = "gpio-keys";
        pinctrl-names = "default";
	status = "disabled";


&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        key2_imx6ull: key2_imx6ull {                /*!< Function assigned for the core: Cortex-A7[ca7] */
            fsl,pins = <
                MX6UL_PAD_NAND_CE1_B__GPIO4_IO14           0x000010B0
            >;
        };


&iomuxc_snvs {
    pinctrl-names = "default_snvs";
    pinctrl-0 = <&pinctrl_hog_2>;
    imx6ul-evk {
        key1_imx6ull: key1_imx6ull {        /*!< Function assigned for the core: Cortex-A7[ca7] */
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01        0x000110A0
            >;
        };

gpio_key_drv.c (驱动程序)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
};

static struct gpio_key *gpio_keys_imx6ull;


static int major = 0;
static struct class *gpio_key_class;


#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);


static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;
	
	wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}



static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	int key;
	
	val = gpiod_get_value(gpio_key->gpiod);
	

	printk("key %d %d\n", gpio_key->gpio, val);
	key = (gpio_key->gpio << 8) | val;
	put_key(key);
	wake_up_interruptible(&gpio_key_wait);
	
	return IRQ_HANDLED;
}

/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_imx6ull = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{
		gpio_keys_imx6ull[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_imx6ull[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_imx6ull[i].gpiod = gpio_to_desc(gpio_keys_imx6ull[i].gpio);
		gpio_keys_imx6ull[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_imx6ull[i].irq  = gpio_to_irq(gpio_keys_imx6ull[i].gpio);
	}

	for (i = 0; i < count; i++)
	{
		err = request_irq(gpio_keys_imx6ull[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "my_gpio_key", &gpio_keys_imx6ull[i]);
	}

	
	major = register_chrdev(0, "my_gpio_key", &gpio_key_drv);  

	gpio_key_class = class_create(THIS_MODULE, "my_gpio_key_class");
	if (IS_ERR(gpio_key_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "my_gpio_key");
		return PTR_ERR(gpio_key_class);
	}

	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "my_gpio_key"); /* /dev/my_gpio_key */
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;

	device_destroy(gpio_key_class, MKDEV(major, 0));
	class_destroy(gpio_key_class);
	unregister_chrdev(major, "my_gpio_key");

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_imx6ull[i].irq, &gpio_keys_imx6ull[i]);
	}
	kfree(gpio_keys_imx6ull);
    return 0;
}


static const struct of_device_id imx6ull_keys[] = {
    { .compatible = "my,gpio_key" },
    { },
};


static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "my_gpio_key",
        .of_match_table = imx6ull_keys,
    },
};


static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver);
	
	return err;
}


static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");

button_test.c  (应用程序)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./button_test /dev/my_gpio_key
 *
 */
int main(int argc, char **argv)
{
	int fd;
	int val;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	while (1)
	{
		/* 3. 读文件 */
		read(fd, &val, 4);
		printf("get button : 0x%x\n", val);
	}
	
	close(fd);
	
	return 0;
}

Makefile文件

KERN_DIR = /home/usr/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o button_test button_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  button_test


obj-m += gpio_key_drv.o

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值