介绍

不同的编程语言具有不同的抽象原语(如下),有的原语抽象层次低,有的原语抽象层次高。其中函数式、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设备模型继承实现细节局部图:

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

领域特定语言(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)传给内核用于设备的扫描、配置和初始化。详细的启动流程如下:

  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语言编程范式的更多相关文章

  1. Linux基础与Linux下C语言编程基础

    Linux基础 1 Linux命令 如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 登录Linux后,我们就可以在#或$符后面去输入命令,有 ...

  2. 【转】Linux基础与Linux下C语言编程基础

    原文:https://www.cnblogs.com/huyufeng/p/4841232.html ------------------------------------------------- ...

  3. Linux下C语言编程实现spwd函数

    Linux下C语言编程实现spwd函数 介绍 spwd函数 功能:显示当前目录路径 实现:通过编译执行该代码,可在终端中输出当前路径 代码实现 代码链接 代码托管链接:spwd.c 所需结构体.函数. ...

  4. LINUX下C语言编程基础

    实验二 Linux下C语言编程基础 一.实验目的 1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用 ...

  5. LINUX下C语言编程调用函数、链接头文件以及库文件

    LINUX下C语言编程经常需要链接其他函数,而其他函数一般都放在另外.c文件中,或者打包放在一个库文件里面,我需要在main函数中调用这些函数,主要有如下几种方法: 1.当需要调用函数的个数比较少时, ...

  6. Linux下C语言编程基础学习记录

    VIM的基本使用  LINUX下C语言编程 用gcc命令编译运行C语言文件 预处理阶段:将*.c文件转化为*.i预处理过的C程序. 编译阶段:将*.i文件编译为汇编代码*.s文件. 汇编阶段:将*.s ...

  7. linux下C语言编程,include的默认搜索路径

    C语言编程时,发现细节的魅力很大.较为详细了看了一下关于include的知识,发现了几点新知: 1.include<头文件名>和include"头文件名" 如:incl ...

  8. Linux下C语言编程中库的使用

    零.问题 1. 为什么要用到库? 2. 我要用一个库,但是,尼玛命令行上该怎么写呢?或者说库文件如何使用? 3. Linux的库在那些地方? 4. 什么是静态库,什么是动态库,二者有啥区别? 5. 常 ...

  9. 实验二 Linux下C语言编程基础

    1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用gdb调试技术 6. 熟悉makefile基本原理 ...

随机推荐

  1. Spring Cloud微服务实践之路-起始

    由于各种原因,公司要对现有的营销产品进行微服务化,如果可以,则对公司所有产品逐步进行微服务化. 而本人将探索这条路,很艰难,但干劲十足.整个过会记录下来,以便以后查阅. 感谢公司!感谢领导! 相关书籍 ...

  2. 记我在github上参与的Star增长最快的十万级项目。。。

    前言 GitHub作为程序员的圣地. 用了两三年,一直都觉得,他可以代码托管,项目管理,为项目建立静态主页,个人简历,找工作,面试加分. 然而>>>....昨天才认识到我还是太年轻, ...

  3. sleep、wait、notify、notifyAll的区别

    Sleep 和wait 1. sleep是Thread类的静态方法,wait是Object类中定义的方法2. Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.s ...

  4. 关于操作HDFS的一个问题

    近日写程序定时任务调Hadoop MR程序,然后生成报表,发送邮件,当时起了两个任务A和B,调MR程序之前,会操作hdfs(读写都有),任务A每天一点跑,任务B每十分钟跑一次,B任务不会调用MR程序, ...

  5. 读spring源码(三)-ClassPathXmlApplicationContext-getBean

    这次主要看了下bean的生成过程,发现个画时序图很好用的软件plantuml,充分发挥程序员的能力,能用代码解决的别叨叨别的

  6. Linux initramfs说明

    1.前言 最近在尝试对手头的开发板进行移植,此处记录initramfs挂载的基本流程,记录一下,以备后查.分析时是基于linux3.4.2 2. rootfs的挂载 start_kernel-> ...

  7. 彻底搞懂 C# 的 async/await

    前言 Talk is cheap, Show you the code first! private void button1_Click(object sender, EventArgs e) { ...

  8. c++入门篇七

    拷贝构造函数的调用时机: class Person { public: //构造函数 Person() { //无参构造函数 } Person(int a) { //有参构造函数 cout <& ...

  9. IIS+nginx反向代理 负载均衡

    本文没有过多的讲述,只讲述重点地方.由这两个转自的文章进行中和 1.nginx+iis实现负载均衡(这篇文章主要是有第2篇文章的工具) 2.nginx+iis使用(这篇文章讲得很详细,配置文件直接复制 ...

  10. Map,HashMap,LinkedHashMap,TreeMap比较和理解

    /* * 获取功能: * V get(Object key):根据键获取值 * Set<K> keySet():获取集合中所有键的集合 * Collection<V> valu ...