contained_of

工作队列传参优化

利用遍历查找

⛺️定义了btn_work_content结构体,包含两个关键成员:

  • 一个work_struct用于工作队
  • 一个指向btn_resource的指针

像给每个工作队列贴上了一个标签,告诉它"你是负责处理哪个按键的"

⛺️当内核的工作队列线程执行这个函数时,它只会收到一个work_struct指针,不知道这个work属于哪个按键。循环判断,把传入的work指针和数组中的每个btn_work进行比较。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>

struct btn_resource{
	char* name;
	int gpio;
	int irq;
};
static struct btn_resource btn_info[]={
	{ .name = "KEY_UP" , .gpio =PAD_GPIO_A +28 },
	{ .name = "KEY_DOWN" , .gpio =PAD_GPIO_B +30 },
	{ .name = "VOL_UP" , .gpio =PAD_GPIO_B +31 },
	{ .name = "VOL_DOWN" , .gpio =PAD_GPIO_B +9 }		
};
#define BTN_COUNT ARRAY_SIZE(btn_info)

/*需要建立工作队列和按键的联系*/
struct btn_work_content{
	struct work_struct btn_work;
	struct btn_resource* key;
};
static struct btn_work_content btn_content[BTN_COUNT];

static void btn_handle(struct btn_work_content* cet){
	struct btn_resource* p=cet->key;
	int kstate=gpio_get_value(p->gpio);
	pr_info("The %s is %s!\n",p->name,kstate?"released":"pressed");

}
static void btn_work_fun(struct work_struct* work){
	int i;
	for(i=0;i<BTN_COUNT;i++){
		if(work == &btn_content[i].btn_work){
			btn_handle(&btn_content[i]);
			return;
		}
	}
}

//中断处理函数
static irqreturn_t button_isr(int irq,void* dev){
	//注册工作队列
	struct btn_work_content* pdata=(struct btn_work_content*)dev;
	schedule_work(&pdata->btn_work);
	return IRQ_HANDLED;	
}
static int __init btn_init(void){
	int i;
	int ret;
	for(i=0;i<BTN_COUNT;i++){
		struct btn_resource* p=&btn_info[i];
		gpio_request(p->gpio,p->name);
		gpio_direction_input(p->gpio);
		p->irq=gpio_to_irq(p->gpio);
		//建立按键和工作队列的联系
		btn_content[i].key=p;
		//初始化工作队列
		INIT_WORK(&btn_content[i].btn_work,btn_work_fun);
		ret=request_irq(p->irq,button_isr,
				   IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING,
				   p->name,&btn_content[i]);
		if(ret){
			pr_err("request_irq fails\n");
			continue;
		}
	}
	pr_info("the workqueue driver is loaded!\n");
	return 0;
}
static void __exit btn_exit(void){
	int i;
	for(i=0;i<BTN_COUNT;i++){
		struct btn_resource* p=&btn_info[i];
		//取消工作队列
		free_irq(p->irq,&btn_content[i]);//禁止新的中断	
		cancel_work_sync(&btn_content[i].btn_work);
		gpio_free(p->gpio);
	}
	pr_info("the workqueue driver is removed!\n");
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

如何建立按键和工作队列的联系

在这里插入图片描述


中断处理函数参数传的结构体指针
在这里插入图片描述

注意参数

在这里插入图片描述

需要额外的数据结构来维护映射关系,需要遍历查找,代码更复杂,效率更低

利用container_of

这种结构体关联思想在按键非常多时候就不适用了,而且循环判断会牺牲一部分性能

先介绍offsetof
#include <stdio.h>
#include <stddef.h> //offsetof

//结构体
struct MyInfo1{
	char a;
	int b;
	double c;
};
struct MyInfo2{
	int a;
	double b;
	char c;
};
//%zu:z是size_t,u是unsigned_int
int main(){
	printf("MyInfo1 : offsetof a is %zu!\n",offsetof(struct MyInfo1,a ));
	printf("MyInfo1 : offsetof b is %zu!\n",offsetof(struct MyInfo1,b));
	printf("MyInfo1 : offsetof c is %zu!\n",offsetof(struct MyInfo1,c ));
	printf("MyInfo1 total size is %zu!\n",sizeof(struct MyInfo1));
	printf("MyInfo2: offsetof a is %zu!\n",offsetof(struct MyInfo2,a ));
	printf("MyInfo2 : offsetof b is %zu!\n",offsetof(struct MyInfo2,b ));
	printf("MyInfo2 : offsetof c is %zu!\n",offsetof(struct MyInfo2,c ));
	printf("MyInfo2 total size is %zu!\n",sizeof(struct MyInfo2));
	return 0;
}

%zu-----z:size_t , u:unsigned

offsetof宏的作用是计算结构体中某个成员相对于结构体起始位置的字节偏移量

输出结果:

在这里插入图片描述

这里就是内存对齐


MyInfo1的内存布局:

先是a占1字节,然后是3字节的填充,接着是b占4字节,最后是c占8字节

  • int类型通常需要在4字节边界上对齐才能高效访问,下一个可用的4字节对齐位置就是地址4。a实际占一字节编译器在a后面悄悄插入了3个填充字节来实现这个对齐要求。
  • double类型通常需要8字节对齐,double c从偏移量8开始,这正好是8字节边界对齐。

MyInfo2的内存布局:

先是a占4字节,然后是4字节的填充,接着是b8字节,最后是c占1字节,填充7字节成为8的整数倍

  • 成员b需要8字节对齐,而位置8是下一个可用的对齐地址,所以在a之后又插入了4个字节的填充,使得b能从偏移量8开始。
  • c只占1字节,那么结构体总共需要16加1等于17字节,但17不是8的倍数,所以编译器会在结构体末尾再填充7字节,使总大小变成24字节。

⛺️​offsetof(结构体类型, 成员名)

  • 第一个参数是结构体的类型(注意要写完整的struct 结构体名)
  • 第二个参数是你想查询的成员名称。
  • 这个宏会返回一个size_t类型的值,表示该成员距离结构体开头的字节数。

在这里插入图片描述

类型在前,成员在后

什么是container_of

container_of宏想要解决的问题:

通过一个指向某个结构体的某个成员的指针,反推出整个结构体的起始地址


实际例子:

struct Student {
 int id;           // 学号,假设占4字节
 char name[20];    // 姓名,占20字节
 double score;     // 成绩,占8字节
};

目前知道这个学生的名字,想得到学生的成绩。也就是参数是指向成绩的指针,把指推导出学生信息的指针。

无类型安全的简化版宏

在这里插入图片描述

char类型的大小恰好是一个字节,把ptr转换成char *类型后,指针运算就变成了以字节为单位


减法运算(char *)(ptr) - offsetof(type, member)

这一步就是用成员的地址减去它的偏移量,得到结构体的起始地址。

注意这里得到的结果是char *类型,

//内核里常用的 container_of 宏的完整版写法,多了类型安全
#define container_of(ptr, type, member) ({                      \
    const typeof(((type *)0)->member) *__mptr = (ptr);          \
    (type *)((char *)__mptr - offsetof(type, member));          \
})

这个宏的正确性完全依赖于你传入的三个参数是准确对应的。如果传入的ptr实际上不是指向type类型结构体中的member成员,那么计算出来的地址就是错误的,后续访问会导致未定义行为,可能引发段错误或者更糟糕的内存损坏。

#include <stdio.h>
#include <stddef.h> //offsetof

//结构体
struct Student {
 int id;           // 假设从偏移量0开始,占4字节
 char name[20];    // 假设从偏移量4开始,占20字节
 double score;     // 假设从偏移量24开始,占8字节
};


//contained_of宏需要自己定义,内核有包含它的头文件
#define container_of(ptr, type, member) \
 ((type *)((char *)(ptr) - offsetof(type, member)))

int main(){
	//printf("Student : offsetof a is %zu!\n",offsetof(struct Student,id ));
	//printf("Student : offsetof b is %zu!\n",offsetof(struct Student,name));
	//printf("Student : offsetof c is %zu!\n",offsetof(struct Student,score));
	//printf("Student total size is %zu!\n",sizeof(struct Student));

	struct Student stu={1345,"liuyifei",90};
	//已知指向分数的指针
	double *score_ptr=&stu.score;

	//利用contained_of反推机构体指针
	struct Student* getValue=container_of(score_ptr,struct Student, score);

	//打印其他信息
	printf("name is %s,id is %d!\n",getValue->name,getValue->id);
	return 0;
}

结构体成员的指针->结构体指针->访问其他成员

利用container_of宏简化程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>

struct btn_resource{
	char* name;
	int gpio;
	int irq;
	struct work_struct btn_work;//成员,非指针
};
static struct btn_resource btn_info[]={
	{ .name = "KEY_UP" , .gpio =PAD_GPIO_A +28 },
	{ .name = "KEY_DOWN" , .gpio =PAD_GPIO_B +30 },
	{ .name = "VOL_UP" , .gpio =PAD_GPIO_B +31 },
	{ .name = "VOL_DOWN" , .gpio =PAD_GPIO_B +9 }		
};
#define BTN_COUNT ARRAY_SIZE(btn_info)

static void btn_work_fun(struct work_struct* work){
	int kstate;
	struct btn_resource* pdata=container_of(work,struct btn_resource,btn_work);
	kstate=gpio_get_value(pdata->gpio);
	pr_info("The %s is %s\n",pdata->name,kstate?"released":"pressed");
}

//中断处理函数
static irqreturn_t button_isr(int irq,void* dev){
	//注册工作队列
	struct btn_resource* pdata=(struct btn_resource*)dev;
	schedule_work(&pdata->btn_work);
	return IRQ_HANDLED;	
}
static int __init btn_init(void){
	int i;
	int ret;
	for(i=0;i<BTN_COUNT;i++){
		struct btn_resource* p=&btn_info[i];
		gpio_request(p->gpio,p->name);
		gpio_direction_input(p->gpio);
		p->irq=gpio_to_irq(p->gpio);
		
		//初始化工作队列
		INIT_WORK(&btn_info[i].btn_work,btn_work_fun);
		ret=request_irq(p->irq,button_isr,
				   IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING,
				   p->name,p);
		if(ret){
			pr_err("request_irq fails\n");
			continue;
		}
	}
	pr_info("the container_of driver is loaded!\n");
	return 0;
}
static void __exit btn_exit(void){
	int i;
	for(i=0;i<BTN_COUNT;i++){
		struct btn_resource* p=&btn_info[i];
		free_irq(p->irq,p);
		cancel_work_sync(&btn_info[i].btn_work);
		gpio_free(p->gpio);
	}
	pr_info("the container_of driver is removed!\n");
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

注意函数类型传参

在这里插入图片描述


这里使用了container_of宏,这是个非常经典的C语言技巧。通过work_struct成员的地址,反向计算出包含它的btn_resource结构体的起始地址,这样就能访问到按键的名称和GPIO信息了。
在这里插入图片描述

首先把work_struct放入按键中,代表一个按键一个驱动程序

中断处理函数传参是btn_resource*类型,然后可以利用该类型存在work_struct成员给函数传惨

点睛之笔就是:btn_work_fun函数通过work反推出btn_resource类型

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐