Linux Kernel C語言編程範式

介紹

不一樣的編程語言具備不一樣的抽象原語(以下),有的原語抽象層次低,有的原語抽象層次高。其中函數式、DSL是這幾年十分熱門的編程語言概念。html

  • 過程式抽象原語:變量
  • 對象式抽象原語:對象
  • 函數式抽象原語:函數
  • 事件驅動抽象原語:事件
  • DSL抽象原語:業務定製語言

Linux kernel個與硬件打交道、用C語言開發的幾十年巨型軟件項目。它的開發語言是C,做爲一門過程式語言,好像離對象式、函數式、DSL這些編程範式很遠,沒法將這些優秀的編程範式的威力發揮在Linux Kernel項目上node

可是,果然如此麼?linux

面對對象式Linux Kernel編程

面對對象編程介紹

 wikipedia對面對對象編程的定義:web

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).express

從中能夠看出,面對對象式編程的基本特徵:編程

  • 封裝 – 保護數據的能力
  • 抽象 – 定義數據的能力
  • 繼承與多態 – 複用數據的能力

無論是用什麼編程語言,只要能知足這些特徵,那就是面對對象範式。C++、Java語言由於提供了對這些特徵直接表達的語法,因此對面對對象編程十分友好。雖然C語言沒有這些原語支持,可是一樣也能作到面對對象。設計模式

封裝

封裝的特色:數據結構

  • 信息隱藏
  • 代碼解耦
  • 減小編譯依賴
  • 面向接口編程
  • OCP的前提

封裝的實現方法:閉包

  • 模塊的數據結構做爲內部屬性,不對外暴露。數據結構類型定義放在模塊c文件中,h頭文件只放數據結構類型聲明
  • 模塊對外導出的外部接口參數中若是使用了數據結構,參數形式使用指針,h頭文件只放對外導出的外部接口和數據結構類型聲明

封裝示例:app

  • 示例一: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)傳給內核用於設備的掃描、配置和初始化。詳細的啓動流程以下:

  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 系統總線、設備等

 

 

--完--

相關文章
相關標籤/搜索