最近我在看linux的内核源码其中有好多非常优秀的代码写法今天要描述的就是“函数转数据”这种用法。“函数转数据”是我自己起的名称可能不太准确具体含义是在一个源文件模块内部定义了好多static修饰的静态函数一般用法源文件中另外一个函数去直接调用这个函数但是“函数转数据”的用法是这样的用到了函数指针变量例如定义了一个结构体类型结构体成员包含各种函数指针类型的变量然后另一一个结构体类型的全局变量全局变量初始化的时候被赋绑定的函数名最后通过访问这个全局变量里面的成员来间接实现对函数的访问。例如如下操作在input.h中定义了一个结构体类型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中的各个静态函数。static struct input_handler sysrq_handler { .filter sysrq_filter, .connect sysrq_connect, .disconnect sysrq_disconnect, .name sysrq, .id_table sysrq_ids, };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如下所示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间接实现了对绑定函数的访问。整个过程有点像一个大项目的分工工作各干各的各自负责各自工工作最后再全局变量内部成员进行绑定后就整个线路就通了。举个例子有点像城市电网小区负责小区电网网络建设这个网络现在是未通电的市政的国家电网负责城市主要干线的铺设最终通过小区变压器之类节点实现小区电网连接市电工作。真正静态函数定义相当于小区网络建设结构体变量定义相当于“电网节点联网”结构体变量访问相当于“国家电网电网铺设”。整个过程的好处就是各个各自的互不干涉另外就是实现了“隔离”互补干涉。一方改动不会影响第二方。最后我们追踪一下结构体变量成员再哪里是真正访问的。技巧就是“追踪”结构体类型定义时的成员变量。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面向对象编程有助于大项目协同工作各干各的互补影响在一个节点实现双方绑定。这样编写代码好处多多就不再论述了但是也带来了一个因为隔离带来的不容易把控整个流程的弊端需要一定抽象思维来捋清楚整个过程很难从一方跳转到绑定节点再跳转到另外一方来理解清楚。
面向对象:linux内核中函数转数据的用法
最近我在看linux的内核源码其中有好多非常优秀的代码写法今天要描述的就是“函数转数据”这种用法。“函数转数据”是我自己起的名称可能不太准确具体含义是在一个源文件模块内部定义了好多static修饰的静态函数一般用法源文件中另外一个函数去直接调用这个函数但是“函数转数据”的用法是这样的用到了函数指针变量例如定义了一个结构体类型结构体成员包含各种函数指针类型的变量然后另一一个结构体类型的全局变量全局变量初始化的时候被赋绑定的函数名最后通过访问这个全局变量里面的成员来间接实现对函数的访问。例如如下操作在input.h中定义了一个结构体类型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中的各个静态函数。static struct input_handler sysrq_handler { .filter sysrq_filter, .connect sysrq_connect, .disconnect sysrq_disconnect, .name sysrq, .id_table sysrq_ids, };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如下所示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间接实现了对绑定函数的访问。整个过程有点像一个大项目的分工工作各干各的各自负责各自工工作最后再全局变量内部成员进行绑定后就整个线路就通了。举个例子有点像城市电网小区负责小区电网网络建设这个网络现在是未通电的市政的国家电网负责城市主要干线的铺设最终通过小区变压器之类节点实现小区电网连接市电工作。真正静态函数定义相当于小区网络建设结构体变量定义相当于“电网节点联网”结构体变量访问相当于“国家电网电网铺设”。整个过程的好处就是各个各自的互不干涉另外就是实现了“隔离”互补干涉。一方改动不会影响第二方。最后我们追踪一下结构体变量成员再哪里是真正访问的。技巧就是“追踪”结构体类型定义时的成员变量。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面向对象编程有助于大项目协同工作各干各的互补影响在一个节点实现双方绑定。这样编写代码好处多多就不再论述了但是也带来了一个因为隔离带来的不容易把控整个流程的弊端需要一定抽象思维来捋清楚整个过程很难从一方跳转到绑定节点再跳转到另外一方来理解清楚。