介绍
不同的编程语言具有不同的抽象原语(如下), 有的原语抽象层次低, 有的原语抽象层次高. 其中函数式, DSL 是这几年十分热门的编程语言概念.
过程式抽象原语: 变量
对象式抽象原语: 对象
函数式抽象原语: 函数
事件驱动抽象原语: 事件
DSL 抽象原语: 业务定制语言
Linuxkernel 是个与硬件打交道, 用 C 语言开发的几十年的巨型软件项目. 它的开发语言是 C, 作为一门过程式语言, 好像离对象式, 函数式, DSL 这些编程范式很远, 无法将这些优秀的编程范式的威力发挥在 Linux Kernel 项目上.
但是, 果真如此么?
面对对象式 Linux Kernel 编程
面对对象编程介绍
wikipedia 对面对对象编程的定义:
Object-oriented programming attempts to provide a model for programming based on objects. Object-oriented programming integrates code and data using the concept of an "object". An object is an abstract data type with the addition of polymorphism and inheritance. An object has both state (data) and behavior (code).
从中可以看出, 面对对象式编程的基本特征:
封装 - 保护数据的能力
抽象 - 定义数据的能力
继承与多态 - 复用数据的能力
不管是用什么编程语言, 只要能满足这些特征, 那就是面对对象范式. C++,Java 语言因为提供了对这些特征直接表达的语法, 所以对面对对象编程十分友好. 虽然 C 语言没有这些原语支持, 但是同样也能做到面对对象.
封装
封装的特点:
信息隐藏
代码解耦
减少编译依赖
面向接口编程
OCP 的前提
封装的实现方法:
模块的数据结构作为内部属性, 不对外暴露. 数据结构类型定义放在模块 c 文件中, h 头文件只放数据结构类型声明
模块对外导出的外部接口参数中如果使用了数据结构, 参数形式使用指针, h 头文件只放对外导出的外部接口和数据结构类型声明
封装示例:
示例一: A 模块头文件 scan.h 中要声明接口: int ubi_scan_add_used(struct ubi_device *ubi, struct ubi_scan_info *si, int pnum, int ec, const struct ubi_vid_hdr *vid_hdr, int bitflips); 而 struct ubi_vid_hdr 的类型定义在 ubi-media.h.scan.h 不应该 #include "ubi-media.h", 而是声明 struct ubi_vid_hdr;
示例二: 数据类型 struct ubi_volume_desc 只在某个 c 文件中实现中使用, 因此数据类型 struct ubi_volume_desc 放在这个 c 文件中定义, 在其头文件中声明 struct ubi_volume_desc 类型, 导出接口的参数使用这个类型指针.
抽象, 继承与多态在《C 语言面对对象设计模式汇编》一文中有详细介绍, 不再赘述.
Linux 设备模型面对对象设计
Linux 设备模型是 Linux Kernel 中抽象编程的最佳范本, 它分解抽象设备模型 6 个最基本的对象(如下), 其他所有对象由这些对象组合派生而来.
device: 抽象设备
device_driver: 抽象驱动
bus_type: 抽象 device 和 driver 的关系
kobject: 抽象设备的公共属性和行为(如层次结构描述, 生命周期管理, 热插拔, 用户态呈现等)
kset: 抽象设备组的公共行为(如热插拔事件)
kobj_type: 抽象设备组的公共属性(如用户态呈现)
Linux 设备模型继承关系示图:
Linux 设备模型继承实现细节局部图:
函数式 Linux Kernel 编程
函数式编程介绍
wikipedia 对函数式编程的定义:
functional programming is a programming paradigm, a style of building the structure and elements of computer programs, thattreats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions.
函数式编程的基本特征:
- Immutable data
- First class function
- Tail Recursive Opti
函数式编程常用技术:
- Higher order function
- map/reduce
- Closure
- Recursing
- Pipline
- Lazy evaluation
一等函数
函数是函数式编程的 "一等公民", 可以在任何位置定义, 使用, 如变量, 函数入参, 返回值. 这一点 C 语言完全可以做到, Kernel 中也有不少编程实例, 如下面这个示例中 crystalhd_get_cmd_proc 就是个高阶函数, 它的返回值是一个函数指针.
- typedef enum BC_STATUS(*crystalhd_cmd_proc)(struct crystalhd_cmd *,
- struct crystalhd_ioctl_data *);
- crystalhd_cmd_proc crystalhd_get_cmd_proc(struct crystalhd_cmd *ctx,
- uint32_t cmd, struct crystalhd_user *uc){
- crystalhd_cmd_proc cproc = NULL;
- for (i = 0; i <tbl_sz; i++) {
- // 删除不相关代码, 以便与展示 ...
- cproc = g_crystalhd_cproc_tbl[i].cmd_proc;
- break;
- }
- }
- return cproc;
- }
闭包
闭包是高阶函数的一种表现形式, 可以理解为函数与其环境数据的结合体. 它主要有 2 个作用:
控制流抽象
命名空间控制
C 语言的主要有如下应用场景:
遍历集合
管理资源
实施策略
如下的示例中, device_for_each_child 就符合闭包的定义: 是函数 fn 与其环境数据 data 的结合体.
- int device_for_each_child(struct device *parent, void *data,
- int (*fn)(struct device *dev, void *data)){
- struct klist_iter i; struct device *child; int error = 0;
- klist_iter_init(&parent->p->klist_children, &i);
- while ((child = next_device(&i)) && !error)
- error = fn(child, data);
- klist_iter_exit(&i);
- return error;
- }
- device_for_each_child(dev, NULL, device_check_offline);
- result = device_for_each_child(dev, addrp, i2cdev_check_mux_children);
- device_for_each_child(&dev->dev, &status, slot_reset_iter);
事件驱动 Linux Kernel 编程
事件驱动编程介绍
wikipedia 对事件驱动编程的定义:
Event-driven programming is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key presses), sensor outputs, or messages from other programs/threads. Event-driven programming is the dominant paradigm used in graphical user interfaces and other applications (e.g. JavaScript web applications) that are centered on performing certain actions in response to user input.
事件驱动编程的优点:
代码解耦
时间解耦
事件的定义:
用户行为
中断
定时器
信号
消息
...
事件驱动编程的实现原则:
好莱坞原则
依赖倒置原则
事件驱动编程的实现三部曲:
事件注册
事件处理
事件循环(转化, 合并, 排队, 分派等)
事件驱动编程的结构化设计:
事件注册: 高层, 应用模块
事件处理: 高层, 功能模块
事件循环: 底层, 抽象层, 核心模块
事件驱动编程的实现技术:
回调函数: 控制反转
抽象接口: 依赖注射
Kernel 热插拔事件设计
热插拔事件定义
- enum kobject_action {
- KOBJ_ADD,
- KOBJ_REMOVE,
- KOBJ_CHANGE,
- KOBJ_MOVE,
- KOBJ_ONLINE,
- KOBJ_OFFLINE,
- KOBJ_MAX
- };
热插拔消息格式定义
- "add@/class/input/input9/mouse2\0 // message
- ACTION=add\0 // action type
- DEVPATH=/class/input/input9/mouse2\0 // path in /sys
- SUBSYSTEM=input\0 // subsystem (class)
- SEQNUM=1064\0 // sequence number
- PHYSDEVPATH=/devices/pci0000:00/0000:00:1d.1/usb2/2-2/2-2:1.0\0 // device path in /sys
- PHYSDEVBUS=usb\0 // bus
- PHYSDEVDRIVER=usbhid\0 // driver
- MAJOR=13\0 // major number
- MINOR=34\0", // minor number
热插拔事件驱动框架
热插拔事件驱动工作流程:
中断, 用户输入作为事情源
定义事件处理行为(如 device_uevent_ops)// 事件处理
通过 kset_create_and_add 进行事件注册 // 事件注册
内核调用 kobject_uevent 进行事件循环, 对事件进行过滤, 构造, 转化等处理 // 事件循环
将 uevent 事件转换成 netlink 消息, 调用 netlink_broadcast_filtered 进行 socket 广播(udev 事件源) // 事件循环
用户态 udevd 监听事件, 并进一步事件处理, 如构造 dev 文件, 调用用户态命令等. // 事件循环
领域特定语言(DSL) Linux Kernel 编程
领域特定语言介绍
wikipedia 对事件驱动编程的定义:
A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains, and lacks specialized features for a particular domain.
领域特定语言又分为内部 DSL 和外部 DSL, 它们具有共同的特征:
领域语义
元编程
内部 DSL
内部 DSL 是嵌入到开发语言内部, 与开发语言混合使用的 DSL, 它可以是一个接口, 如 printf, 也可以是一个宏, 如下示例.
UNUSUAL_DEV( 0x0421, 0x0446, 0x0100, 0x0100, "Nokia", "N80", US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY )
UNUSUAL_DEV 呈现了 2 种信息, 一种是设备 id_table 信息, 用于驱动匹配, 一种是 unusual_dev_list, 用于标示非标准设备. 具体设计和实现细节可以参考《Linux 设备驱动框架设计》一文中的 "USB 块设备驱动框架设计" 小节, 不再赘述.
外部 DSL
外部 DSL 独立于开发语言使用, 自身具有一定的语言完备性.
Linux Kernel 中的设备树描述模型是个很好的外部 DSL 的例子. 如下图 (左) 所示, 它描述的是系统中的设备层次关系, 这种 DSL 与领域模型 (如下图右) 处在同一语义层次上, 表达的语法基本就是领域语言, 十分贴切自然.
设备树描述文件 (DTS) 经过解释器 (DTC) 转成成字节描述文件 (DTB),DTB 通过引导加载程序(bootloader) 传给内核用于设备的扫描, 配置和初始化. 详细的启动流程如下:
通过 dtc 将 dts 编译成 dtb
Boot 阶段对 fdt 进一步完善调整(如 clock_freq, chosen 节点等)
Boot 通过 do_bootm_linux ()引导内核, 并将 fdt 基址传给内核
内核调用 machine_init (), early_init_devtree ()获取 bootargs 等参数
内核调用 start_kernel(),setup_arch(),unflatten_device_tree()函数来解析 dtb 文件, 构造 of_allnodes 链表
内核调用 OF 提供的 of_platform_bus_probe 等接口获取 of_allnodes 链表信息来 device_add 系统总线, 设备等
-- 完 --
来源: https://www.cnblogs.com/wahaha02/p/10147639.html