介紹
不同的編程語言具有不同的抽象原語(如下),有的原語抽象層次低,有的原語抽象層次高。其中函數式、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 = 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.
事件驅動編程的優點:
- 代碼解耦
- 時間解耦
事件的定義:
- 用戶行為
- 中斷
- 定時器
- 信號
- 消息
- 。。。
事件驅動編程的實現原則:
- 好萊塢原則
- 依賴倒置原則
事件驅動編程的實現三部曲:
- 事件注冊
- 事件處理
- 事件循環(轉化、合並、排隊、分派等)
事件驅動編程的結構化設計:
- 事件注冊:高層、應用模塊
- 事件處理:高層、功能模塊
- 事件循環:底層、抽象層、核心模塊
事件驅動編程的實現技術:
- 回調函數:控制反轉
- 抽象接口:依賴注射
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)傳給內核用於設備的掃描、配置和初始化。詳細的啟動流程如下:
- 通過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 系統總線、設備等
--完--