Linux Kernel C语言编程范式

介绍

不同的编程语言具有不同的抽象原语(如下),有的原语抽象层次低,有的原语抽象层次高。其中函数式、DSL是这几年十分热门的编程语言概念。

  • 过程式抽象原语:变量
  • 对象式抽象原语:对象
  • 函数式抽象原语:函数
  • 事件驱动抽象原语:事件
  • DSL抽象原语:业务定制语言

Linux kernel是个与硬件打交道、用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 Kernel C语言编程范式

Linux设备模型继承实现细节局部图:

Linux Kernel C语言编程范式

函数式Linux Kernel编程

函数式编程介绍

wikipedia对函数式编程的定义:

functional programming is a programming paradigm, a style of building the structure and elements of computer programs, that treats 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 = ; 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 = ;
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.

事件驱动编程的优点:

  • 代码解耦
  • 时间解耦

事件的定义:

  • 用户行为
  • 中断
  • 定时器
  • 信号
  • 消息
  • 。。。

事件驱动编程的实现原则:

  • 好莱坞原则
  • 依赖倒置原则

事件驱动编程的实现三部曲:

  • 事件注册
  • 事件处理
  • 事件循环(转化、合并、排队、分派等)

事件驱动编程的结构化设计:

  • 事件注册:高层、应用模块
  • 事件处理:高层、功能模块
  • 事件循环:底层、抽象层、核心模块

事件驱动编程的实现技术:

  • 回调函数:控制反转
  • 抽象接口:依赖注射

Linux设备热插拔事件驱动设计

热插拔事件定义

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文件、调用用户态命令等。 //事件循环

Linux Kernel C语言编程范式

领域特定语言(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与领域模型(如下图右)处在同一语义层次上,表达的语法基本就是领域语言,十分贴切自然。

Linux Kernel C语言编程范式Linux Kernel C语言编程范式

设备树描述文件(DTS)经过解释器(DTC)转成成字节描述文件(DTB),DTB通过引导加载程序(bootloader)传给内核用于设备的扫描、配置和初始化。详细的启动流程如下:

  1. 通过dtc将dts编译成dtb
  2. Boot阶段对fdt进一步完善调整(如clock_freq, chosen节点等)
  3. Boot通过do_bootm_linux ()引导内核,并将fdt基址传给内核
  4. 内核调用machine_init (), early_init_devtree ()获取bootargs等参数
  5. 内核调用start_kernel()、setup_arch()、unflatten_device_tree()函数来解析dtb 文件,构造of_allnodes链表
  6. 内核调用OF 提供的of_platform_bus_probe等接口获取of_allnodes链表信息来device_add 系统总线、设备等

Linux Kernel C语言编程范式

--完--

上一篇:Introduction to Financial Management


下一篇:bit、sbin、sfr、sfr16 区别分析