1. 程式人生 > >Linux Kernel C語言程式設計正規化

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裝置模型繼承實現細節區域性圖:

 

函式式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.

事件驅動程式設計的優點:

  • 程式碼解耦
  • 時間解耦

事件的定義:

  • 使用者行為
  • 中斷
  • 定時器
  • 訊號
  • 訊息
  • 。。。

事件驅動程式設計的實現原則:

  • 好萊塢原則
  • 依賴倒置原則

事件驅動程式設計的實現三部曲:

  • 事件註冊
  • 事件處理
  • 事件迴圈(轉化、合併、排隊、分派等)

事件驅動程式設計的結構化設計:

  • 事件註冊:高層、應用模組
  • 事件處理:高層、功能模組
  • 事件迴圈:底層、抽象層、核心模組

事件驅動程式設計的實現技術:

  • 回撥函式:控制反轉
  • 抽象介面:依賴注射 

Kernel熱插拔事件設計

熱插拔事件定義

enum kobject_action {
 KOBJ_ADD,
 KOBJ_REMOVE,
 KOBJ_CHANGE,
 KOBJ_MOVE,
 KOBJ_ONLINE,
 KOBJ_OFFLINE,
 KOBJ_MAX
};

 熱插拔訊息格式定義

"[email protected]/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 系統匯流排、裝置等

 

 

--完--