0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux驱动开发-内核共享工作队列

DS小龙哥-嵌入式技术 2022-09-17 15:03 次阅读

【摘要】 在工作队列里,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统有默认的工作者线程,自己也可以创建自己的工作者线程。

1. 内核工作队列

工作队列常见的使用形式是配合中断使用,在中断的服务函数里无法调用会导致休眠的相关函数代码,有了工作队列机制以后,可以将需要执行的逻辑代码放在工作队列里执行,只需要在中断服务函数里触发即可,工作队列是允许被重新调度、睡眠。

在工作队列里,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统有默认的工作者线程,自己也可以创建自己的工作者线程。

2. 相关函数、结构介绍

2.1 工作结构

定义文件:
Workqueue.h (linux-3.5\include\Linux)

原型:
struct work_struct {
		atomic_long_t data;    
		struct list_head entry;
		work_func_t func;      /* 工作函数指针 */
	#ifdef CONFIG_LOCKDEP
		struct lockdep_map lockdep_map;
	#endif
	};

在工作结构体里,只需要关心一个成员函数:work_func_t func;
这个成员函数是一个函数指针,指向工作函数的指针;内核使用这个结构来描述一个工作,一个工作简单理解就是对应于一个函数,可以通过内核调度函数来调用work_struct中func指针所指向的函数。

2.2 工作函数介绍

定义文件   Workqueue.h (linux-3.5\include\linux)
函数原型   typedef void (*work_func_t)(struct work_struct *work);
功能	    这是指向工作函数地址的函数指针,编写一个工作的函数。
参数	   struct work_struct *work,这个参数,指向struct work_struct结构变量本身。

示例:

struct work_struct work;
INIT_WORK(&work, work_func);
初始化一个work结构,work_func工作函数的参数就是指向work结构。

2.3 初始化宏

1)初始化一个work结构:
INIT_WORK(_work, _func)	     
_work: struct work_struct work结构指针。
_func:用来填充work_struct work结构的fun成员,就是工作函数指针。

2)共享工作队列调度宏:
schedule_work(_work)
它也是一个宏,作用是调度一个工作_work。
_work:要调度工作的结构指针;
示例:
schedule_work(&work)

2.4 使用共享工作队列的步骤

1)定义一个工作结构变量
struct work_struct work; 

2)初始化工作结构(重点func成员)。
先编写一个工作函数:
void work_func(struct work_struct * dat)
{
   printk(“%p:”,dat);
   ……
}
初始化work:
INIT_WORK(&work, work_func);

3)在适当的地方调度工作
如果工作用于中断底部代码,则在中断顶部调度。
	schedule_work(&work);
不是马上执行,而是等待CPU空闲才执行work_func。

3. 案例代码

3.1 共享工作队列-按键驱动

下面这份代码是在一个按键驱动代码,在按键中断服务函数里调度共享队列,最终在工作函数里完成按键值的检测打印。工作队列采用的是共享工作队列。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct work_struct work;

static struct m_key_info *key_info_p=NULL;

/*存放按键的信息*/
struct m_key_info
{
	int gpio;
	char name[50];
	int val;
	int irq;
};

struct m_key_info key_info[]=
{
	{EXYNOS4_GPX3(2),"key_irq_1",0x01},
	{EXYNOS4_GPX3(3),"key_irq_2",0x02},
	{EXYNOS4_GPX3(4),"key_irq_3",0x03},
	{EXYNOS4_GPX3(5),"key_irq_4",0x04},
};
/*
工作函数
*/
static void key_work_func(struct work_struct *work)
{
	msleep(50);
	//udelay(n);
	//mdelay(n);
	//msleep(unsigned int msecs);
	
	if(gpio_get_value(key_info_p->gpio)==0) //判断按键是否按下
	{
		printk("按键值:%#x\n",key_info_p->val);
	}
	else
	{
		printk("按键值:%#x\n",key_info_p->val|0x80);
	}
}
/*
中断服务函数
*/
static irqreturn_t key_irq_handler(int irq, void *dev)
{
    key_info_p=(struct m_key_info*)dev;

	/*调度工作----工作结构体添加到系统共享工作队列里*/
	schedule_work(&work);
	
	return IRQ_HANDLED;
}

static int __init tiny4412_interrupt_drv_init(void)
{
    /*初始化工作*/
	INIT_WORK(&work,key_work_func);
	
	int i;
	for(i=0;i
	3.2 自定义工作队列-按键驱动

工作队列除了可以使用内核共享队列以外,也可以自己创建队列,下面这份代码就演示如何自己创建队列,并完成初始化、调用。代码原型还是一份按键驱动代码,与上面代码相比,加了字符设备节点注册,替换系统共享工作队列为自定义的工作队列。

#include 
#include 
#include  /*杂项设备相关结构体*/
#include          /*文件操作集合头文件*/
#include     /*使用copy_to_user和copy_from_user*/
#include          /*使用IO端口映射*/
#include        
#include      /*设备*/
#include        /*标准字符设备--分配设备号*/
#include       /*ioctl操作*/
#include   /*注册中断相关*/
#include  		  /*中断边沿类型定义*/
#include  	  /*中断IO口定义*/
#include       /*内核定时器相关*/
#include        /*等待队列相关*/
#include       /*等待队列相关*/
#include        /*POLL机制相关*/
#include  /*自旋锁相关*/
#include        /*自旋锁相关*/
#include           /*原子操作相关*/
#include          /*原子操作相关*/
#include           /*延时函数*/
#include 
#include          /*信号相关头文件*/
#include       /*工作队列相关*/

/*----------------------------------------------------
	创建自己的工作队列creator_workqueue测试
-----------------------------------------------------*/

/*定义ioctl的命令*/
#define Cmd_LEDON  _IO('L',1)   //无方向    --开灯
#define Cmd_LEDOFF  _IO('L',0)   //无方向  ---关灯

/*定义设备号注册相关*/
static dev_t keydev;            //存放设备号
static struct cdev *keyCdev;    //定义cdev结构体指针
static struct class *cls;       //定义类结构体指针

/*定义按键中断相关*/
static unsigned int irq_buff[4]; /*存放中断编号*/
static int key_value=0;          /*存放按键按下的键值*/

/*定时器相关*/
struct timer_list my_timer;

/*全局标志*/
static int poll_flag=0;

struct mutex ; /*  互斥锁  */

/*等待队列相关*/
static DECLARE_WAIT_QUEUE_HEAD(wait);/*初始化等待队列头*/
static int condition=0;            /*唤醒队列的条件-为假休眠-为真唤醒*/

/*异步通知助手相关*/
static struct fasync_struct *myfasync; 

/*信号量*/
static DEFINE_SEMAPHORE(name_sem);

/*内核工作队列相关结构体*/
static struct work_struct my_work;

/*延时工作队列相关结构体*/
static struct delayed_work my_delay_work;

/*创建自己的工作队列相关*/
struct workqueue_struct *my_work_queue;

struct Buttons_data
{
   char key_name[10]; /*按键的名字*/
   char key;           /*按键值*/
   int  GPIO;          /*GPIO口编号*/
};
/*工作队列的处理函数*/
static void my_work_func(struct work_struct *work)
{
	static int count=0;
	printk("\n\n用户创建的系统共享工作队列调度成功%d 次\n\n",count++);
}

/*结构体整体赋值*/
static struct Buttons_data Key_interrupt[4]=
{
	{"key1",0x01,EXYNOS4_GPX3(2)},
	{"key2",0x02,EXYNOS4_GPX3(3)},
	{"key3",0x03,EXYNOS4_GPX3(4)},
	{"key4",0x04,EXYNOS4_GPX3(5)},
};
/*按键中断服务函数*/
static irqreturn_t irq_handler_function(int irq,void * dat)
{
   struct Buttons_data *p =(struct Buttons_data *)dat; /*强制转换*/
   
   if(!gpio_get_value(p->GPIO))
   {
		key_value=p->key;            /*获取按下按键值*/
   }
   else
   {
		key_value=p->key|0x80;      /*获取松开按键值*/
   }
   mod_timer(&my_timer,jiffies+1); /*修改超时时间*/
   return IRQ_HANDLED;
}
/*定时器中断服务函数*/
static void timer_function(unsigned long data)
{
	printk("按键值读取成功!!0x%x--->!\n",key_value);
    /*添加延时工作到系统工作队列中等待执行*/
   // schedule_delayed_work(&my_delay_work,HZ*5);
	//queue_work(my_work_queue,&my_work); /*调度共享工作队列*/
	queue_delayed_work_on(-1,my_work_queue,&my_delay_work,HZ*5);
}

static int key_open(struct inode *my_inode, struct file *my_file)
{
    unsigned char i;
	for(i=0;i<4;i++)
	{
		//获取中断编号
	   irq_buff[i]=gpio_to_irq(EXYNOS4_GPX3(2+i));
	   request_irq(irq_buff[i],irq_handler_function,IRQ_TYPE_EDGE_BOTH,Key_interrupt[i].key_name,&Key_interrupt[i]);
	}

	/*定时器相关*/
	my_timer.expires=0;/*1秒钟*/
    my_timer.function=timer_function;/*定时器中断处理函数*/
    my_timer.data=888; /*传递给定时器中断服务函数的参数-用于共享定时器*/
    init_timer(&my_timer);        /*初始化定时器*/
	add_timer(&my_timer);	       /*启动定时器*/
	
	printk("open ok !\n");
	return 0;
}

static ssize_t key_read(struct file *my_file, char __user *buf, size_t my_conut, loff_t * my_loff)
{
    int error=0;
	error=copy_to_user(buf,&key_value,my_conut); /*向应用层拷贝按键值*/
	key_value=0;

	if(!error)
	{
		return 0;         /*没有读取成功*/
	}
	else
	{
		return my_conut; /*返回成功读取的字节数*/
	}
}

static  ssize_t key_write(struct file *my_file, const char __user *buf, size_t my_conut, loff_t *my_loff)
{
    int error;
	printk("write ok !\n");
	return 1;
}
 
static long key_unlocked_ioctl(struct file *my_file, unsigned int cmd, unsigned long argv)
{
   int dat;
  /*只有传递地址的时候才需要转换-----*/
	void __user *argv1=(void __user*)argv;          //强制转换地址
     printk("argv1=%ld\n",*(unsigned long*)argv1);	//取出数据

	 argv=(unsigned long*)argv;  /*转为指针形式*/
    
    switch(cmd)
	{
		case Cmd_LEDON:
			dat=100;
			copy_to_user(argv,&dat,4);
			printk("LEDON_----->OK\n");
			break;
	
		case Cmd_LEDOFF:
			dat=200;
			copy_to_user(argv,&dat,4);
			printk("LEDOFF_----->OK\n");
			break;
	}
	return 0;
}

/*poll--*/
unsigned int my_poll(struct file *my_file, struct poll_table_struct * p)
{ 
    /*唤醒休眠的进程*/  
	poll_wait(my_file,&wait,p);/*添加等待队列--不是立即休眠*/
	printk("<1>""8888\n");
	 if(condition==1)
	 {
	    printk("drive----poll ----ok!\n");
	    condition=0;   /*清除标志*/
		return POLLIN;  /*返回事件*/  
	 }
	return 0;  /*返回事件*/  
}

/*异步通知助手*/
int key_fasync(int fd, struct file *my_file,int on)  //异步通知
{
	int error;
	printk("驱动层收到的文件描述符:%d\n",fd);
	error=fasync_helper(fd,my_file,on,&myfasync);
	printk("驱动层异步通知结构体文件描述符:%d\n",myfasync->fa_fd);
	return error;
}

static int  key_release(struct inode *my_inode, struct file *my_file)
{
   int i;
	//释放中断
    for(i=0;i<4;i++)
   	{
   		free_irq(irq_buff[i],&Key_interrupt[i]); 
	}
	return 0;
}

/*定义一个文件操作集合结构体*/
static struct file_operations ops_key={
   .owner = THIS_MODULE,
   .read=key_read,       /*读函数-被应用层read函数调用*/
   .write=key_write,     /*写函数-被应用层write函数调用*/
   .open=key_open,       /*打开函数-被应用层open函数调用*/
   .release=key_release, /*释放函数*/
   .unlocked_ioctl=key_unlocked_ioctl,  /*ioctl操作*/
   .poll=my_poll,         /*poll机制*/
   .fasync=key_fasync,    /*异步通知助手*/
};

static int __init key_init1(void)
{
  /*动态分配一个设备号*/
   alloc_chrdev_region(&keydev,0,1,"mykey"); //我们可以读取/proc/devices文件以获得Linux内核分配给设备的主设备号和设备名字
  /*动态分配cdev结构体,返个cdev结构;如果执行失败,将返回NULL。*/
   keyCdev = cdev_alloc();
  /*初始化Cdev结构体*/
   cdev_init(keyCdev,&ops_key);
  /*注册Cdev结构体*/
   cdev_add(keyCdev,keydev,1);
  /*创建类*/
   cls=class_create(THIS_MODULE,"my_key");
   /*在类下面创建设备*/
    device_create(cls,NULL,keydev,NULL,"my_delaywork");//  /dev/

   /*创建自己的工作队列*/
	my_work_queue =create_workqueue("my_workqueue");
	/*初始化延时工作队列*/
	 INIT_DELAYED_WORK(&my_delay_work,my_work_func);

    /*初始化无延时的工作队列*/
	// INIT_WORK(&my_work,my_work_func);
	
	 printk("<1>""key drive init OK!!-->__FILE__=%s  __LINE__=%d\n",__FILE__,__LINE__);
	return 0;
}

//KERN_EMERG
static void __exit key_exit(void)
{
	device_destroy(cls,keydev); 		   //注销设备节点	
	class_destroy(cls);				   //注销分配的类	
	cdev_del(keyCdev);					   //注销CDEV结构体	
	unregister_chrdev_region(keydev,1); //注销设备
	kfree(keyCdev);                      //释放结构体
    printk("<1>""key drive exit OK!! -->__FILE__=%s  __LINE__=%d\n",__FILE__,__LINE__);
}
//EXPORT_SYMBOL(key_init);
module_init(key_init1);  /*驱动入口*/
module_exit(key_exit);  /*驱动出口*/
MODULE_LICENSE("GPL");
(key_info)>(key_info)/sizeof(key_info[0]);i++)>
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5056

    文章

    18955

    浏览量

    301675
  • 内核
    +关注

    关注

    3

    文章

    1359

    浏览量

    40179
  • 函数
    +关注

    关注

    3

    文章

    4276

    浏览量

    62303
收藏 人收藏

    评论

    相关推荐

    deepin社区亮相第19届中国Linux内核开发者大会

    中国 Linux 内核开发者大会,作为中国 Linux 内核领域最具影响力的峰会之一,一直以来都备受瞩目。
    的头像 发表于 10-29 16:35 281次阅读

    linux内核中通用HID触摸驱动

    linux内核中,为HID触摸面板实现了一个通用的驱动程序,位于/drivers/hid/hid-multitouch.c文件中。hid触摸驱动是以struct hid_driver
    的头像 发表于 10-29 10:55 136次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b>中通用HID触摸<b class='flag-5'>驱动</b>

    迅为iTOP-RK3568开发驱动开发指南-第十八篇 PWM

    实验 第43章 特殊的软中断tasklet分析实验 第44章 共享工作队列实验 第45章 自定义工作队列实验 第46章 延迟工作实验 第47章 工作
    发表于 10-29 10:13

    linux驱动程序如何加载进内核

    Linux系统中,驱动程序是内核与硬件设备之间的桥梁。它们允许内核与硬件设备进行通信,从而实现对硬件设备的控制和管理。 驱动程序的编写
    的头像 发表于 08-30 15:02 334次阅读

    linux驱动程序的编译方法是什么

    Linux驱动程序的编译方法主要包括两种: 与内核一起编译 和 编译成独立的内核模块 。以下是对这两种方法的介绍: 一、与内核一起编译 与
    的头像 发表于 08-30 14:46 334次阅读

    Linux 驱动开发与应用开发,你知道多少?

    一、Linux驱动开发与应用开发的区别开发层次不同:Linux
    的头像 发表于 08-30 12:16 477次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>驱动</b><b class='flag-5'>开发</b>与应用<b class='flag-5'>开发</b>,你知道多少?

    Linux内核测试技术

    Linux 内核Linux操作系统的核心部分,负责管理硬件资源和提供系统调用接口。随着 Linux 内核的不断发展和更新,其复杂性和代码规
    的头像 发表于 08-13 13:42 403次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>测试技术

    欢创播报 华为宣布鸿蒙内核已超越Linux内核

    1 华为宣布鸿蒙内核已超越Linux内核   6月21日,在华为开发者大会上, HarmonyOS NEXT(鸿蒙NEXT)——真正独立于安卓和iOS的鸿蒙操作系统,正式登场。这是Ha
    的头像 发表于 06-27 11:30 748次阅读

    Linux 6.9-rc1发布,加入定时器、工作队列及AMD P-State优化

    内核方面,6.9版本进行了定时器的大幅重构,增加了每个CPU核心的时间轮支持,以提升定时器运效率,尤其在网络应用中表现出色。此外,工作队列子系统新增BH工作队列支持,摒弃了老旧的tasklet机制。
    的头像 发表于 03-25 13:49 405次阅读

    C++在Linux内核开发中从争议到成熟

    Linux 内核邮件列表中一篇已有六年历史的老帖近日再次引发激烈讨论 —— 主题是建议将 Linux 内核开发语言从 C 转换为更现代的
    的头像 发表于 01-31 14:11 563次阅读
    C++在<b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>开发</b>中从争议到成熟

    rk3399移植Linux内核

    RK3399是一款由中国厂商瑞芯微推出的高性能处理器芯片,被广泛用于嵌入式系统开发。在进行应用程序开发之前,我们需要将Linux内核移植到RK3399上,以支持硬件的
    的头像 发表于 01-08 09:56 978次阅读

    Linux 6.8主线内核将支持骁龙8 Gen 3,但仍需额外适配

     Linux 6.8主线内核对高通骁龙8 Gen 3的支持将为开发人员和用户带来更多的选择和便利。无需额外的适配工作Linux操作系统将能
    的头像 发表于 12-19 15:46 1664次阅读

    获取Linux内核源码的方法

    (ELF1/ELF1S开发板及显示屏)Linux内核是操作系统中最核心的部分,它负责管理计算机硬件资源,并提供对应用程序和其他系统组件的访问接口,控制着计算机的内存、处理器、设备驱动
    的头像 发表于 12-13 09:49 603次阅读
    获取<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>源码的方法

    AD5592r linux驱动不支持linux4.1的内核,怎么修改?

    AD5592r linux驱动不支持linux4.1的内核,不知道怎么修改,以适配linux4.1的内核
    发表于 12-06 07:17

    Linux内核驱动与单个PCI设备的绑定和解绑定

    Linux内核2.6.13-rc3以前,驱动和设备之间的绑定和解绑只能通过insmod(modprobe)和rmmod来实现,但是这种实现方法有一个弊端,就是一旦绑定或者解绑定都是针对驱动
    的头像 发表于 11-17 17:11 1493次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>驱动</b>与单个PCI设备的绑定和解绑定