谷动谷力

 找回密码
 立即注册
查看: 1186|回复: 0
打印 上一主题 下一主题
收起左侧

Linux 内核的 C 语言编程范式

[复制链接]
跳转到指定楼层
楼主
发表于 2023-3-7 23:24:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
Linux 内核的 C 语言编程范式



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

  • 过程式抽象原语:变量

  • 对象式抽象原语:对象

  • 函数式抽象原语:函数

  • 事件驱动抽象原语:事件

  • DSL抽象原语:业务定制语言

Linux kernel 是个与硬件打交道、用 C 语言开发的几十年的巨型软件项目。它的开发语言是 C,作为一门过程式语言,好像离对象式、函数式、DSL这些编程范式很远,无法将这些优秀的编程范式的威力发挥在 Linux Kernel 项目上。

但是,果真如此么?

2. 面向对象式Linux Kernel编程2.1 面向对象编程介绍

面向对象编程的定义:

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语言没有这些原语支持,但是同样也能做到面对对象。

2.2 封装

封装的特点:

  • 信息隐藏
  • 代码解耦
  • 减少编译依赖
  • 面向接口编程
  • 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类型,导出接口的参数使用这个类型指针。
2.3 Linux设备模型面向对象设计

Linux设备模型是Linux Kernel中抽象编程的最佳范本,它分解抽象设备模型 6 个最基本的对象,其他所有对象由这些对象组合派生而来。

  • device抽象设备
  • device_driver抽象驱动
  • bus_type抽象device和driver的关系
  • kobject抽象设备的公共属性和行为(如层次结构描述、生命周期管理、热插拔、用户态呈现等)
  • kset抽象设备组的公共行为(如热插拔事件)
  • kobj_type抽象设备组的公共属性(如用户态呈现)

Linux设备模型继承关系示图:

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

3. 函数式 Linux Kernel 编程3.1 函数式编程介绍

函数式编程的定义:

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
3.2 一等函数

函数是函数式编程的“一等公民”,可以在任何位置定义、使用,如变量、函数入参、返回值。这一点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.cmd_proc;
            break;
        }
    }
    return cproc;
}
3.3 闭包

闭包是高阶函数的一种表现形式,可以理解为函数与其环境数据的结合体。它主要有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);
4. 事件驱动Linux Kernel编程4.1 事件驱动编程介绍

事件驱动编程的定义:

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.

事件驱动编程的优点:

  • 代码解耦
  • 时间解耦

事件的定义:

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

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

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

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

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

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

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

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

  • 回调函数:控制反转
  • 抽象接口:依赖注射
4.2 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文件、调用用户态命令等。
热插拔事件处理流程5. 领域特定语言(DSL) Linux Kernel编程5.1 领域特定语言介绍

DSL编程的定义:

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,它们具有共同的特征:

  • 领域语义
  • 元编程
5.2 内部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,用于标示非标准设备。

5.3 外部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 系统总线、设备等


设备树处理流程

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-4-26 00:42 , Processed in 0.188534 second(s), 42 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表