嵌入式C語言自我修養 13:總結

13.1 總結

前面12節的課程,主要針對 Linux 內核中 GNU C 擴展的一些常用 C 語言語法進行了分析。GNU C 的這些擴展語法,主要用來完善 C 語言標準和編譯優化。而通過 C 標準的發展過程我們又發現,對於一些編譯器擴展的一些特性,或者其它編程語言(如:C++)中的好的特性和語法,C 標準也會適時地吸收進來,作爲新的 C 語言標準。

在 GNU C 的這些擴展語法中,attribute 和宏定義是兩大特色。在嵌入式底層系統中,尤其是 Linux 內核和 U-boot 中,大量使用 GNU C 擴展的 attribute屬性去輔助一些底層機制的實現,或者實現一些編譯上的優化。在宏定義方面,通過語句表達式、可變參數宏等特性,我們可以定義一個功能複雜、安全可靠的高質量宏。

本教程所講的一些特性,都是在實際工作或閱讀 Linux 內核驅動源碼時經常遇見的一些特性,掌握了這些擴展特性的使用,以後再遇到類似的“奇葩 C 語言”程序,就知道怎麼去分析了。除此之外,GNU C 還有一些其它擴展特性,由於他們在內核中用得不是很多,或者說僅僅是做一些編譯上的優化,即使不知道也不會影響我們理解代碼,限於篇幅關係,所以就暫時不講了,比如下面這些特性。

  • 屬性聲明:const
  • 屬性聲明:constructor、destructor
  • 屬性聲明:noreturn
  • 屬性聲明:used、unused
  • 局部標籤
  • 嵌套函數
  • ……

大家以後遇到類似的擴展,可以到下面這幾個網站上去看看。

  • GNU C語法擴展大全
  • GCC 編譯器手冊

13.2 C 語言習題測試

下面是幾道 C 語言練習題,大家可以做一做。看看學完本教程後,有沒有真正的掌握。有什麼疑問,可以通過讀者圈,或加入QQ羣(475504428)與我討論。

1.下面的程序,在不同編譯環境下,比如分別在 C-Free、VC++6.0、TurboC 環境下編譯運行,結果是否相等,爲什麼?

#include<stdio.h>
int main(void)
{
    printf("size: %d\n", sizeof(int);
    return 0;
}

2.定義一個宏,求兩個數的最小值。

3.將下面的程序編譯爲可以在 ARM 平臺上運行的可執行文件 a.out,並對其進行反彙編,查看變量 global_val 的地址。

int global_val = 10;
int uninit_val;
int main(void)
{
    int local_val = 20;
    return 0;
}
  1. 在一個工程項目中,有兩個源文件如下,分析下面程序的運行結果。

    //func.c
    int a = 10;
    int b;
    int c attribute((weak)) = 30;

    //main.c
    int a;
    int b = 20;
    int c = 40;
    int main(void)
    {
    printf("a: %d\n",a);
    printf("b: %d\n",b);
    printf("c: %d\n",c);
    return 0;
    }

5.定義一個變參函數,實現等級打印控制:ERROR、DEBUG、INFO。用這三個宏分別代表等級打印,比如定義 ERROR 時,只打印錯誤的信息;定義 DEBUG 時,打印錯誤和調試信息;定義 INFO 時,所有的打印信息都打印出來。

6.定義一個變參宏,實現等級打印控制:ERROR、DEBUG、INFO。用這三個宏分別代表等級打印,比如定義 ERROR 宏時,只打印錯誤的信息;定義 DEBUG 時,打印錯誤和調試信息;定義 INFO 時,所有的打印信息都打印出來。

7.下面是 Linux 內核(Linux4.4.0)中的一些宏定義,請分析它們實現的功能。

#define pr_emerg(fmt, ...) \
    printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...) \
    printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...) \
    printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...) \
    printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...) \
    printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...) \
    printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) \
    printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

8.在 Linux 內核啓動過程中,啓動 log 的最後往往會有這麼一行信息。

Freeing unused kernel memory: 468K

請用本課程中的 section 屬性聲明,分析這段 log 背後的內核初始化及內存釋放過程。

9.在嵌入式 Linux 驅動開發中,驅動模塊是沒有 main() 入口函數的,請用本課程學過的知識分析:驅動是如何運行和初始化的。

10.驅動分析:在 linux4.4 源碼 linux-4.4/arch/arm/mach-footbridge/ebsa285.c 中,分析下面代碼的含義及 container_of 宏的作用。

MACHINE_START(EBSA285, "EBSA285")
    /* Maintainer: Russell King */
    .atag_offset    = 0x100,
    .video_start    = 0x000a0000,
    .video_end  = 0x000bffff,
    .map_io     = footbridge_map_io,
    .init_early = footbridge_sched_clock,
    .init_irq   = footbridge_init_irq,
    .init_time  = footbridge_timer_init,
    .restart    = footbridge_restart,
MACHINE_END
static void ebsa285_led_set(struct \
led_classdev *cdev, enum led_brightness b)
{
    struct ebsa285_led *led = container_of(cdev,
            struct ebsa285_led, cdev);

    if (b == LED_OFF)
        hw_led_state |= led->mask;
    else
        hw_led_state &= ~led->mask;
    writeb(hw_led_state, xbus);
}

static enum led_brightness \
ebsa285_led_get(struct led_classdev *cdev)
{
    struct ebsa285_led *led = container_of(cdev,
            struct ebsa285_led, cdev);

    return hw_led_state & led->mask ? LED_OFF : LED_FULL;
}

13.3 結束語

通過本課程的學習,再加上本節10個習題的練習,相信大家的 C 語言功底肯定又加深了一層!有了這些知識儲備基礎,基本上就掃除了 Linux 內核的閱讀障礙。相信大家在以後的工作、學習中一定會日益精進,不斷突破!

最後祝大家工作順利、學習愉快!

另外,大家如果想系統學習 Linux 內核中的某塊知識,或者說有哪些知識掌握得不是很好,想進階學習,但限於工作、學習繁忙,時間精力有限,無法系統地去學習,也可以跟我聯繫交流

  • 我的QQ:3284757626)
  • 我的博客:www.zhaixue.cc
  • 我會繼續編寫相關的的知識和教程,爲大家服務。

本教程根據 C語言嵌入式Linux高級編程視頻教程 第05期 改編,電子版書籍可加入QQ羣:475504428 下載,更多嵌入式視頻教程,可關注:
微信公衆號:宅學部落(armlinuxfun)
51CTO學院-王利濤老師:http://edu.51cto.com/sd/d344f

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章