Linux驱动开发基础__工作队列

news2024/11/29 4:48:22

目录

1 引入

2 内核函数

 2.1 定义work

2.2 使用 work:schedule_work 

2.3 其他函数

 3 代码

3.1 gpio_key_drv.c

 3.2 button_test.c

4 内部机制

4.1 Linux 2.x 的工作队列创建过程

4.2   Linux 4.x 的工作队列创建过程 


1 引入

前面讲的定时器、下半部 tasklet,它们都是在中断上下文中执行,它们无法休眠。当要处理更复杂的事情时,往往更耗时。这些更耗时的工作放在定时器或是下半部中,会使得系统很卡;并且循环等待某件事情完成也太浪费 CPU 资源了。 
如果使用线程来处理这些耗时的工作,那就可以解决系统卡顿的问题:因为线程可以休眠。  

在内核中,我们并不需要自己去创建线程,可以使用“工作队列 ”(workqueue)。内核初始化工作队列是,就为它创建了内核线程。以后我们要使用“工作队列”,只需要把“工作”放入“工作队列中”,对应的内核线程就会取出“工作”,执行里面的函数。 
在 2.xx 的内核中,工作队列的内部机制比较简单;在现在 4.x 的内核中,工作队列的内部机制做得复杂无比,但是用法是一样的。 
工作队列的应用场合:要做的事情比较耗时,甚至可能需要休眠,那么可以使用工作队列。 
缺点:多个工作(函数)是在某个内核线程中依序执行的,前面函数执行很慢,就会影响到后面的函数。 
在多 CPU 的系统下,一个工作队列可以有多个内核线程,可以在一定程度上缓解这个问题。 

2 内核函数

内核线程、工作队列(workqueue)都由内核创建了,我们只是使用。使用的核心是一个 work_struct 结构体,定义如下:

使用工作队列时,步骤如下: 
第1步 构造一个 work_struct 结构体,里面有函数; 
第2步 把这个 work_struct 结构体放入工作队列,内核线程就会运行 work 中的函数。

 2.1 定义work

参考内核头文件:include\linux\workqueue.h 

#define DECLARE_WORK(n, f)            \ 
  struct work_struct n = __WORK_INITIALIZER(n, f) 
 
#define DECLARE_DELAYED_WORK(n, f)          \ 
  struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0) 

⚫  第 1 个宏是用来定义一个 work_struct 结构体,要指定它的函数。 
⚫  第 2 个宏用来定义一个 delayed_work 结构体,也要指定它的函数。所以“delayed”,意思就是说要让它运行时,可以指定:某段时间之后你再执行。 
如果要在代码中初始化 work_struct 结构体,可以使用下面的宏: 

#define INIT_WORK(_work, _func) 

2.2 使用 work:schedule_work 

调用 schedule_work 时,就会把 work_struct 结构体放入队列中,并唤醒对应的内核线程。内核线程就会从队列里把 work_struct 结构体取出来,执行里面的函数。 

2.3 其他函数

 3 代码

3.1 gpio_key_drv.c

#include <linux/module.h>
#include <linux/poll.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>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
	struct timer_list key_timer;
	struct tasklet_struct tasklet;
	struct work_struct work;
} ;

static struct gpio_key *gpio_keys_100ask;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_key_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#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 void key_timer_expire(unsigned long data)
{
	/* data ==> gpio */
	struct gpio_key *gpio_key = data;
	int val;
	int key;

	val = gpiod_get_value(gpio_key->gpiod);


	printk("key_timer_expire key %d %d\n", gpio_key->gpio, val);
	key = (gpio_key->gpio << 8) | val;
	put_key(key);
	wake_up_interruptible(&gpio_key_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

static void key_tasklet_func(unsigned long data)
{
	/* data ==> gpio */
	struct gpio_key *gpio_key = data;
	int val;
	int key;

	val = gpiod_get_value(gpio_key->gpiod);


	printk("key_tasklet_func key %d %d\n", gpio_key->gpio, val);
}

static void key_work_func(struct work_struct *work)
{
	struct gpio_key *gpio_key = container_of(work, struct gpio_key, work);
	int val;

	val = gpiod_get_value(gpio_key->gpiod);

	printk("key_work_func: the process is %s pid %d\n",current->comm, current->pid);	
	printk("key_work_func key %d %d\n", gpio_key->gpio, val);
}

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
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;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	
	wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_key_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
	.poll    = gpio_key_drv_poll,
	.fasync  = gpio_key_drv_fasync,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	//printk("gpio_key_isr key %d irq happened\n", gpio_key->gpio);
	tasklet_schedule(&gpio_key->tasklet);
	mod_timer(&gpio_key->key_timer, jiffies + HZ/50);
	schedule_work(&gpio_key->work);
	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_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{		
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);

		setup_timer(&gpio_keys_100ask[i].key_timer, key_timer_expire, &gpio_keys_100ask[i]);
		gpio_keys_100ask[i].key_timer.expires = ~0;
		add_timer(&gpio_keys_100ask[i].key_timer);

		tasklet_init(&gpio_keys_100ask[i].tasklet, key_tasklet_func, &gpio_keys_100ask[i]);

		INIT_WORK(&gpio_keys_100ask[i].work, key_work_func);
	}

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

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/gpio_key */

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

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

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	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, "100ask_gpio_key");

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
		del_timer(&gpio_keys_100ask[i].key_timer);
		tasklet_kill(&gpio_keys_100ask[i].tasklet);
	}
	kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
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;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");


 3.2 button_test.c

 


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

static int fd;

/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;
	int ret;
	int	flags;

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


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

	for (i = 0; i < 10; i++) 
	{
		if (read(fd, &val, 4) == 4)
			printf("get button: 0x%x\n", val);
		else
			printf("get button: -1\n");
	}

	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);

	while (1)
	{
		if (read(fd, &val, 4) == 4)
			printf("get button: 0x%x\n", val);
		else
			printf("while get button: -1\n");
	}
	
	close(fd);
	
	return 0;
}


4 内部机制

在 2.xx 版本的 Linux 内核中,创建 workqueue 时就会同时创建内核线程; 
在 4.xx 版本的 Linux 内核中,内核线程和 workqueue 是分开创建的,比较复杂。

4.1 Linux 2.x 的工作队列创建过程

代码在 kernel\workqueue.c 中:

init_workqueues 
keventd_wq = create_workqueue("events"); 
    __create_workqueue((name), 0, 0) 
        for_each_possible_cpu(cpu) { 
            err = create_workqueue_thread(cwq, cpu); 
                p = kthread_create(worker_thread, cwq, fmt, wq->name, cpu);  

对于每一个 CPU,都创建一个名为“events/X”的内核线程,X 从 0 开始。在创建 workqueue 的同时创建内核线程。 

4.2   Linux 4.x 的工作队列创建过程 

Linux4.x 中,内核线程和工作队列是分开创建的。先创建内核线程,代码在 kernel\workqueue.c 中:

init_workqueues 
/* initialize CPU pools */ 
for_each_possible_cpu(cpu) { 
    for_each_cpu_worker_pool(pool, cpu) { 
         /* 对每一个 CPU 都创建 2 个 worker_pool 结构体,它是含有 ID 的 */ 
         /*  一个 worker_pool 对应普通优先级的 work,第 2 个对应高优先级的 work */ 
} 
 
/* create the initial worker */ 
for_each_online_cpu(cpu) { 
    for_each_cpu_worker_pool(pool, cpu) { 
        /* 对每一个 CPU 的每一个 worker_pool,创建一个 worker */  
/* 每一个 worker 对应一个内核线程 */ 
        BUG_ON(!create_worker(pool)); 
    } 
}    

create_worker 函数代码如下:

 创建好内核线程后,再创建 workqueue,代码在 kernel\workqueue.c 中:

init_workqueues 
system_wq = alloc_workqueue("events", 0, 0); 
    __alloc_workqueue_key 
        wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);  // 分配 workqueue_struct 
        alloc_and_link_pwqs(wq) // 跟 worker_poll 建立联系 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/331263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASEMI整流模块MDQ100-16的优点是什么?

编辑-Z ASEMI整流模块MDQ100-16参数&#xff1a; 型号&#xff1a;MDQ100-16 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;1600V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;1700V 最大平均正向整流输出电流&#xff08;IF&#xff0…

【内网安全】——Linux提权姿势

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 一切为了她座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日emo&#xff1a; 希望你在新的一年也能更好&#xff0c;不惧流言蜚语&…

Django drf使用Django自带的用户系统的注册功能实现

在写登录功能的时候看着网上的视频学着做,然后看了源码的时候发现了一些有意思的功能,因此写这一篇笔记分享给大家. 1.阅读Django自带用户系统源码 1.1 阅读User类源码 系统自带的用户系统的models文件的位置\site-packages\django\contrib\auth\models.py,打开这个文件发现…

python爬虫--re模块简介

Python re正则表达式模块用法详解 前面章节介绍了一些系统自带的工具函数&#xff0c;如 id()、max()。这些函数在 Python 启动时会自动加载进来&#xff0c;不需要我们做任何事情。但并不是所有的模块都是自动加载进来的&#xff0c;因为有些模块不常用&#xff0c;它们只是在…

记录一次web server服务器编程过程中的bug

按照书上和视频中的代码比对没有问题&#xff0c;但是read函数输出不了连接的http请求&#xff0c;不断编译了好几遍还是不行&#xff0c;确定是端口的问题 首先&#xff0c;在云服务器中安全规则里已经允许了相应端口&#xff0c;如果还不可以&#xff0c;可以查看一下系统防火…

MyBatis 入门教程详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

配电室辅控项目无线组网远程在线控制系统解决方案

方案背景 配电室是电力系统末端配电网中传输、分配电能的主要电气设备场所&#xff0c;是城市配电网系统的重要组成部分&#xff0c;起到保护、计量、分配电能的作用。 长期以来&#xff0c;配电室管理工作一直是供电系统运行管理的薄弱环节之一。一些配电室开关跳闸、危险气…

【C++提高编程】set/ multiset 容器详解(附测试用例与结果图)

目录1 set/ multiset 容器1.1 set基本概念1.2 set构造和赋值1.3 set大小和交换1.4 set插入和删除1.5 set查找和统计1.6 set和multiset区别1.7 set容器排序1 set/ multiset 容器 1.1 set基本概念 简介&#xff1a; 所有元素都会在插入时自动被排序 本质&#xff1a; set/mu…

搜广推 召回层主流策略 (多路召回、Embedding召回)

😄 简单介绍下召回层的框架,不细致讨论。 文章目录 1、多路召回1.1 多路召回的策略1.2 多路召回的优点和缺点2、Embedding召回2.1、Embedding技术分类2.2、如和做Embedding召回Reference1、多路召回 1.1 多路召回的策略 所谓的“多路召回”策略,就是指采用不同的策略、特…

Linux编辑器vim

本文已收录至《Linux知识与编程》专栏&#xff01; 作者&#xff1a;ARMCSKGT 演示环境&#xff1a;CentOS 7 目录 前言 正文 vim常用方式 进入vim 退出vim vim基本模式及模式功能 命令模式 插入模式 底行模式 替换模式 视图模式 配置vim 自己配置vim 自动化配置…

选择排序基本概念

选择排序基本概念1.选择排序1.1 基本概念1.2 选择排序执行步骤有1.3 对于5个元素的值步骤次数1.4 选择排序大O记法表示2. 将[4,2,7,1,3]进行选择排序 【实战】2.1 第一次轮回步骤2.2 第二次轮回步骤2.3 第三次轮回步骤2.4 第四次轮回步骤3.选择排序代码实现3.1 根据最小值排序3…

TypeScript快速入门

TypeScript快速入门1.TypeScript介绍1.1.TypeScript为什么要为JS添加类型支持1.2.TypeScript相比JS优势2.TypeScript初体验2.1.安装编译TS的工具包2.2.编译并运行TS代码2.3.简化运行TS代码3.TypeScript常用类型3.1.类型注解3.2.常用基础类型3.3.原始类型 number/string/boolean…

前端基础html css js

html&#xff1a;框架 css&#xff1a;美化 jp&#xff1a;交互 HTML 1.基础标签 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>这是我的滴滴滴一个网页</title></head><body><h1>一号 标题&…

Pygame显示文字

使用Pygame显示文字的步骤如图1所示。图1 显示文字的步骤1 Pygame的初始化通过以下代码实现Pygame的初始化。import pygame pygame.init()其中&#xff0c;第1行代码的作用是在程序中导入pygame模块&#xff1b;第2行代码的作用是实现pygame的初始化。2 屏幕的创建使用如下代码…

CacheLib 原理说明

CacheLib 介绍 CacheLib 是 facebook 开源的一个用于访问和管理缓存数据的 C 库。它是一个线程安全的 API&#xff0c;使开发人员能够构建和自定义可扩展的并发缓存。 主要功能&#xff1a; 实现了针对 DRAM 和 NVM 的混合缓存&#xff0c;可以将从 DRAM 驱逐的缓存数据持久…

ESP8266 + STC15基于AT指令通过TCP通讯协议控制IO状态

ESP8266 + STC15基于AT指令通过TCP通讯协议控制IO状态 📌ESP8266 AT固件基于安信可AT固件,相关刷AT固件可以参考《NodeMCU-刷写AT固件》当然WIFI模块也可以是esp01. STC15 单片机采用的是:STC15F2K60S2 晶振频率采用内部:22.1184MHz🌼功能介绍 通过电脑端的网络调试助手…

【C++】类和对象(第一篇)

文章目录1. 面向过程和面向对象初步认识2.类的引入3.类的定义3.1 类的两种定义方式3.2 成员变量命名规则建议4. 类的访问限定符及封装4.1 访问限定符4.2 封装5. 类的作用域6. 类的实例化7. 类对象模型7.1 类对象大小的计算7.2 类对象的存储方式猜测7.3 结构体内存对齐规则复习8…

JDK15 新特性详解,2020-09-15 正式发布

预览版&#xff1a;该功能在当前版本可以使用&#xff0c;如果效果不是很好的话&#xff0c;可能以后的其他版本就会删去该功能。 最终版&#xff1a;该功能在之前版本效果很好&#xff0c;之后的每个版本中都会存在该功能。 Java 5 中的泛型&#xff0c;Java 8 中的 Lambda …

Linux云服务器下怎么重置MySQL8.0数据库密码

文章目录一、修改my.cnf配置文件为mysql免登陆二、免密登陆mysql三.给root用户重置密码1、首先查看当前root用户相关信息&#xff0c;在mysql数据库的user表中2、把root密码置为空3、退出mysql&#xff0c;删除/etc/my.cnf文件中添加进去的skip-grant-tables 重启mysql服务4、使…

FPGA实现AD9708和AD9280波形收发输出HDMI模拟示波器,串口协议帧控制显示,提供工程源码和技术支持

目录1、AD9708芯片解读和电路设计2、AD9280芯片解读和电路设计3、FPGA设计框架4、AD9708波形生成并发送5、AD9280采集接收波形6、HDMI波形显示算法7、串口协议帧控制波形显示8、vivado工程9、上板调试验证10、福利&#xff1a;工程源码获取1、AD9708芯片解读和电路设计 AD9708…