面向对象:linux内核中函数转数据的用法

最近,我在看linux的内核源码,其中有好多非常优秀的代码写法,今天要描述的就是"函数转数据"这种用法。

"函数转数据"是我自己起的名称,可能不太准确,具体含义是:在一个源文件(模块)内部定义了好多static修饰的静态函数,一般用法源文件中另外一个函数去直接调用这个函数,但是"函数转数据"的用法是这样的:用到了函数指针变量,例如定义了一个结构体类型,结构体成员包含各种函数指针类型的变量,然后另一一个结构体类型的全局变量,全局变量初始化的时候被赋绑定的函数名,最后通过访问这个全局变量里面的成员,来间接实现对函数的访问。

例如,如下操作:

在input.h中定义了一个结构体类型:

cpp 复制代码
struct input_handler {

	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	const struct file_operations *fops;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

这里有点面向对象思想的写法,结构体类型内部除了需要的数据之外,还包含了方法,方法就是函数指针类型+函数指针名,组成的结构体成员。

第二步:在sysrq.c中定义了一个静态全局变量:类型为struct input_handler类型,变量名为sysrq_handler,变量初始化中变量被绑定了各种sysrq.c中的各个静态函数。

cpp 复制代码
static struct input_handler sysrq_handler = {
	.filter		= sysrq_filter,
	.connect	= sysrq_connect,
	.disconnect	= sysrq_disconnect,
	.name		= "sysrq",
	.id_table	= sysrq_ids,
};
cpp 复制代码
static bool sysrq_filter(struct input_handle *handle, unsigned int type,
		         unsigned int code, int value)
{
	if (type != EV_KEY)
		goto out;

	switch (code) {

	case KEY_LEFTALT:
	case KEY_RIGHTALT:
		if (value)
			sysrq_alt = code;
		else {
			if (sysrq_down && code == sysrq_alt_use)
				sysrq_down = false;

			sysrq_alt = 0;
		}
		break;

	case KEY_SYSRQ:
		if (value == 1 && sysrq_alt) {
			sysrq_down = true;
			sysrq_alt_use = sysrq_alt;
		}
		break;

	default:
		if (sysrq_down && value && value != 2)
			__handle_sysrq(sysrq_xlate[code], NULL, 1);
		break;
	}

out:
	return sysrq_down;
}

最后,在sysrq.c源文件中直接访问这个全局变量sysrq_handler,如下所示:

cpp 复制代码
static inline void sysrq_register_handler(void)
{
	int error;

	error = input_register_handler(&sysrq_handler);
	if (error)
		pr_err("Failed to register input handler, error %d", error);
	else
		sysrq_handler_registered = true;
}

函数input_register_handler()调用了全局变量sysrq_handler,间接实现了对绑定函数的访问。

整个过程有点像一个大项目的分工工作,各干各的,各自负责各自工工作,最后再全局变量内部成员进行绑定后就整个线路就通了。

举个例子:有点像城市电网,小区负责小区电网网络建设,这个网络现在是未通电的,市政的国家电网负责城市主要干线的铺设,最终通过小区变压器之类节点实现小区电网连接市电工作。

真正静态函数定义相当于小区网络建设,结构体变量定义相当于"电网节点联网",结构体变量访问相当于"国家电网电网铺设"。

整个过程的好处就是各个各自的,互不干涉,另外就是实现了"隔离",互补干涉。一方改动不会影响第二方。

最后,我们追踪一下结构体变量成员再哪里是真正访问的。技巧就是"追踪"结构体类型定义时的成员变量。

cpp 复制代码
int input_register_handler(struct input_handler *handler)		// 输入注册handler
{
	struct input_dev *dev;			// 输入设备
	int retval;						// 返回值

	retval = mutex_lock_interruptible(&input_mutex);			// 上锁
	if (retval)						// 判断reval的值,如果不等于0,表示上锁失败了
		return retval;				// 直接返回

	INIT_LIST_HEAD(&handler->h_list);			// 初始化handler的h_list链表

	if (handler->fops != NULL) {		// 校验handler指向的fops是否为空,如果不为空
		if (input_table[handler->minor >> 5]) {			// struct input_handler * input_table[8]  数量为8表示最多8个input_handler
			retval = -EBUSY;	// 次设备号之所以要除以32(右移5bit),也就是说每一个input_handler下最多挂接32的设备,ls -l /dev/input
			goto out;
		}
		input_table[handler->minor >> 5] = handler;			// 真正干活的,完成注册
	}

	list_add_tail(&handler->node, &input_handler_list);			// 将handler->node添加到input_handler_list中

	list_for_each_entry(dev, &input_dev_list, node)		// 遍历input_handler_list链表
		input_attach_handler(dev, handler);				// 将dev和handler进行挂接

	input_wakeup_procfs_readers();		// input唤醒proc文件系统的readers

 out:
	mutex_unlock(&input_mutex);
	return retval;
}

进入函数input_register_handler()追踪参数:handler的使用情况:发现这个指针变量使用的几个地方没有用到"方法",最有可能的就是子函数input_attach_handler()的第二个参数中有用的"方法",我们再次追踪input_attach_handler()函数。

其中handler->connect就是结构体成员实现指针变量传参+解引用,真正调用的起始就是最初全局变量内部成员绑定的函数。当然了,input_mach_device()函数中也使用了handler指向的函数指针调用。

启迪:

(1)微观:发明函数本身暗含的重要作用:实现隔离,也就说,只要返回值、函数名、参数列表不变,修改函数内部内容不用修改调用函数。这样调用者和被调用者就形成了隔离。架构如下所示:调用函数---》子函数名(参数列表)---》子函数内容。

(2)宏观:从以上面向对象编程思想考虑,也是存在隔离思想的,真正的函数---》注册/绑定函数指针变量---》调用函数指针变量。无论是修改真正的函数,还是修改调用函数指针,两者任意一方修改都不会影响第二方。

(3)面向对象编程,有助于大项目协同工作,各干各的,互补影响,在一个节点实现双方绑定。这样编写代码好处多多,就不再论述了,但是也带来了一个因为隔离带来的不容易把控整个流程的弊端:需要一定抽象思维来捋清楚整个过程,很难从一方跳转到绑定节点,再跳转到另外一方来理解清楚。

相关推荐
姓刘的哦1 小时前
C++软件架构设计思路
linux
ModestCoder_1 小时前
windows/ubuntu解决挂梯子但是codex reconnecting五次的问题
linux·windows·ubuntu
禹凕1 小时前
Linux基础——环境
linux·运维·服务器·ubuntu
好好风格1 小时前
【一行代码】查看本机公网 IP
linux·命令行
落羽的落羽3 小时前
【算法札记】练习 | Week5
linux·服务器·c++·人工智能·计算机网络·算法·哈希算法
Evan_ZGYF丶3 小时前
【开发工具】【perf】Linux下性能分析工具(perf)的使用
linux·嵌入式·开发工具·perf
AC赳赳老秦4 小时前
OpenClaw任务复盘自动化:统计每日完成工作、遗留问题,优化工作节奏
java·大数据·linux·运维·服务器·数据库·openclaw
kaoa0004 小时前
Linux入门攻坚——79、XEN虚拟化-2
linux·运维·开发语言
AOwhisky4 小时前
学习自测(MySQL系列第一期、第二期)
linux·运维·数据库·学习·mysql·云计算