歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> linux內核研究(二)

linux內核研究(二)

日期:2017/3/3 11:54:54   编辑:Linux內核
http://antkillerfarm.github.io/

Linux源代碼編譯

1)按照一般的linux教程上的說法,編譯的第一步,是配置內核的編譯選項。這時有幾種方式可以選擇:從命令行方式的
make config
,到基於ncurse庫的
make menuconfig
,再到基於qt的
make xconfig
和基於GTK+的
make gconfig
。這讓我不得不感歎,即使是內核這樣超底層的東西,居然也會用到GUI。Linus也不總是命令行的擁趸。
不過方式雖多,除了
make config
很不好用之外,其他幾個基本上沒有什麼大的區別。唯一需要注意的是,無論何種方式這一步的目標都是生成.config文件(注意是.config文件,而不是XXX.config文件)。
正是由於有了這一步的存在,定制內核或者說裁剪內核,實際上並沒有想象中那麼高不可攀。不過編譯選項實在是多,好些東西我也不能明白它的真正含義,只能說裁剪過內核,而不敢加上“熟悉”二字…
這裡有個小技巧:
make menuconfig
時,可以按下
/
鍵啟動編譯選項的搜索功能。
2)接著就是make了,根據機器的給力的程度,這個過程會在十分鐘到40分鐘之間。最後生成了vmlinux這個內核鏡像。
3)最後一步,是安裝鏡像,這一步由於比較有風險,我還沒有實際操作。

內核開發心得

從最簡單的內核模塊做起
最近開始研究linux驅動。應該說在驅動領域,我已經有5年以上的工作經驗,不算是新手,但是之前的開發要麼是在嵌入式內核上,要麼就直接是裸機程序,並沒有做過真正的linux驅動程序。所以對於這個特定的領域來說,我就是一個新手。
閒話休提,先從最簡單的可動態加載的模塊說起。
www.ibm.com/developerworks/cn/linux/l-proc.html
這篇文章是我學習的主要參考資料。資料中已經提到的,在此不再贅述。這裡僅作補遺之用。
1)makefile文件的名字必須為Makefile,不然會有編譯錯誤。
2)示例代碼雖然能夠編譯通過,但是insmod之後,會報如下的錯誤:
simple_lkm: module verification failed: signature and/or required key missing - tainting kernel
解決的辦法是在最開頭加上
#include <linux/init.h>

3)自動加載LKM
參考文獻: http://edoceo.com/howto/kernel-modules
以下為節選:
Module Configuration Files
The kernel modules can use two different methods of automatic loading. The first method (modules.conf) is my preferred method, but you can do as you please.
modules.conf - This method load the modules before the rest of the services, I think before your computer chooses which runlevel to use
rc.local - Using this method loads the modules after all other services are started
從這裡可以看出,LKM的加載是要看時機的,如果需要在服務啟動之前加載的話,就修改/etc/modules,否則的話,就修改/etc/rc.local。
PS:/etc/modules由/etc/init/module-init-tools.conf 或 /etc/init/kmod.conf負責執行。
例子見這裡。
proc文件系統
繼續按照上節的參考資料實踐,但是發現create_proc_entry函數老是無法編譯通過。於是找到現在版本的內核代碼進行研究,發現該函數雖然還在用,但已經被定義為內部函數,且僅有一處用到。
這個過程同時也打開了我的思路——還有什麼比內核代碼更豐富的例子庫呢?不管是proc文件系統,還是普通的設備驅動,在內核代碼裡例子比比皆是。
因此,有了下面的例子。
這裡需要注意的是:
1.proc_simple_vfs_write的返回值不能是0。否則的話,一旦用類似
echo "a">/proc/simple-vfs
這樣的方式,向文件寫入數據的時候,proc_simple_vfs_write會一直被反復調用。
2.如果想要用類似
cat /proc/simple-vfs
的方式讀取文件的話,就需要使用seq_open、seq_open、seq_release、seq_printf等一系列以seq開頭的函數。具體的實現可以參照內核中dma.c的代碼。

Uart驅動分析

Write

1.與應用層的接口
這一層的操作是基於文件的。眾所周知,UART屬於TTY設備。因此實際執行的函數是tty_write@tty_io.c。
2.
tty_ldisc.ops->write

3.
n_tty_write@n_tty.c

4.
tty_struct.ops->write => tty_operations->write@serial_core.c

5.
uart_write@serial_core.c

6.
__uart_start@serial_core.c

7.
uart_port.ops->start_tx=>uart_ops->[email protected]

這一層以下,就和具體的設備有關了。這裡以Xlinux的uartlite為例。
8.
ulite_start_tx

9.
ulite_transmit

這裡已經是具體的寄存器操作了。

Read

Read的過程要復雜一些,可分為上層調用部分和底層驅動部分。
上層調用部分:
1.
tty_read@tty_io.c

2.
tty_ldisc.ops->read

3.
n_tty_read@n_tty.c

上層調用,到這裡為止。這個函數執行到add_wait_queue時,會等待底層驅動返回接收的數據。底層驅動可以是中斷式的,也可以是輪詢式的。函數會調用copy_from_read_buf,將內核態的數據搬到用戶態。
底層驅動部分
1.
ulite_startup=>[email protected]

這裡仍以Xlinux的uartlite為例。初始化階段注冊ulite_isr中斷服務程序。
2.
[email protected]

具體的寄存器操作。
3.
tty_flip_buffer_push@tty_buffer.c

4.
tty_schedule_flip@tty_buffer.c

調用schedule_work喚醒上層應用。

select代碼分析

1.
[email protected]

2.
[email protected]

3.
[email protected]

I2C的GPIO實現

概述

I2C的GPIO實現的代碼在drivers/i2c/busses/i2c-gpio.c中。從本質來說這是一個i2c_adapter,它使用i2c_bit_add_numbered_bus函數將自己注冊到I2S總線上。
由於i2c_adapter是直接尋址設備,因此I2C的GPIO實現是以platform driver的方式注冊的,可以在/sys/devices/platform/i2c-gpio.0/i2c-0路徑下查看總線上現有的設備(這裡的0指的是0號i2c總線,某些系統中可能有不止一條i2c總線)。

Write

drivers/i2c/i2c-dev.c: i2cdev_write

drivers/i2c/i2c-core.c: i2c_master_send

drivers/i2c/i2c-core.c: i2c_transfer

drivers/i2c/i2c-core.c: __i2c_transfer

這裡會調用i2c_adapter結構的algo->master_xfer函數。具體到i2c-gpio就是:
drivers/i2c/algos/i2c-algo-bit.c: bit_xfer

drivers/i2c/algos/i2c-algo-bit.c: sendbytes -- 發送字節

drivers/i2c/algos/i2c-algo-bit.c: i2c_outb -- 發送bit

以下以SDA線的操作為例。這裡調用i2c_algo_bit_data結構的setsda函數。具體到i2c-gpio就是:
drivers/i2c/busses/i2c-gpio.c: i2c_gpio_setsda_val

再以下就是具體的GPIO寄存器操作了。

Driver Probe

驅動模塊加載方式

靜態:直接編譯到內核中。
動態:編譯成.ko文件,然後用insmod命令加載之。

驅動分類(按總線類型分)

驅動按設備總線類型分,可分為兩類:
1.直接地址訪問設備。這類設備的驅動被稱為platform driver。
2.間接地址訪問設備,也稱作總線地址訪問設備。這類設備的驅動按總線類型,可分為PCI驅動、USB驅動等等。

驅動的probe函數

作為驅動的實現來說,首先就是要實現驅動的probe函數。probe函數起到了驅動的初始化功能。但之所以叫probe,而不是init,主要是由於probe函數,還具有設備檢測的功能。
以下以s3c24xx_uda134x音頻驅動為例,說一下設備檢測的機制:
1.驅動模塊主要處理兩個對象:設備和驅動。s3c24xx_uda134x屬於platform driver,因此它的設備對象的數據結構是platform_device類型的,而它的驅動對象的數據結構是platform_driver類型的。
2.驅動對象的注冊。使用module_platform_driver宏即可。
3.生成設備對象。
4.設備檢測時,首先根據總線類型,調用總線驅動的probe函數,再根據設備類型調用設備驅動的probe函數。最終完成設備對象和驅動對象的綁定。

module_platform_driver詳解

module_platform_driver定義在include/linux/platform_device.h中。
它的主要內容是注冊和反注冊驅動,以及module_init/module_exit。
介紹module_init/module_exit的文章很多,這裡僅將要點摘錄如下:
1.__init宏修飾的函數會鏈接到.initcall.init段中,這個段只在內核初始化時被調用,然後就被釋放出內存了。
2.定義別名。
int init_module(void) __attribute__((alias(#initfn)));

不管驅動模塊的init函數名字叫什麼,都定義別名為init_module。這樣insmod在加載.ko文件時,就只找init_module函數的入口地址就可以了。

生成設備對象詳解

這裡以mini2440為例,說明一下靜態生成設備對象的過程:
在arch/arm/mach-s3c24xx/mach-mini2440.c中有一個mini2440_devices數組。這個數組定義了板子初始化階段靜態生成的設備對象。
在該文件的mini2440_init函數中,調用platform_add_devices函數,將mini2440_devices數組添加到內核中。
這個例子中和音頻有關的設備為s3c_device_iis、uda1340_codec和mini2440_audio。這三個設備的name分別為s3c24xx-iis、uda134x-codec和s3c24xx_uda134x,與相應驅動名稱一致,正好對應ASOC中的Platform、Codec和Machine。
PC上的情況比較特殊。由於設備數量種類繁多,因此並不采用將所有設備對象都放到一個數組中的方式。而是在驅動模塊加載時,在模塊的init函數中,生成設備對象。某些嵌入式驅動模塊也采用了類似的方法。

probe被調用的流程

drivers/base/driver.c: driver_register

drivers/base/bus.c: bus_add_driver

drivers/base/dd.c: driver_attach

drivers/base/dd.c: _driver_attach

drivers/base/dd.c: driver_probe_device

drivers/base/dd.c: really_probe

模塊初始化

Linux既然由若干模塊組成,那麼這些模塊在啟動階段,必然存在一個加載順序的問題。這方面可以通過以下的宏來確定加載的優先級。
[code]#define pure_initcall(fn)       __define_initcall(fn, 0)
#define core_initcall(fn)       __define_initcall(fn, 1)
#define core_initcall_sync(fn)      __define_initcall(fn, 1s)
#define postcore_initcall(fn)       __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
#define device_initcall(fn)     __define_initcall(fn, 6)
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)
#define late_initcall(fn)       __define_initcall(fn, 7)
#define late_initcall_sync(fn)      __define_initcall(fn, 7s)

上面的宏中,越前面的優先級越高。同一優先級下,按照鏈接順序確定加載順序,因此可以通過修改鏈接文件來修改加載順序,但一般來說,並沒有這個必要。
運行階段的加載,由於是動態加載,沒有加載順序的問題(程序員代碼控制加載順序),因此這些宏都被編譯成module_init。
Copyright © Linux教程網 All Rights Reserved