日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux内核研究(一)

發布時間:2023/12/20 linux 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux内核研究(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://antkillerfarm.github.io/

驅動開發

推薦入門讀物《Beginning Linux Programming》,該書第3版已有中譯本。

但第3版中的例子在2.6以后的新內核中不能編譯。經研究發現,由于新版內核采用KBuild系統編譯內核,所以驅動也必須使用KBuild系統編譯。

驅動開發的頭文件可以在/usr/src下找到。

進階讀物有:

《LINUX設備驅動程序》

《Linux設備驅動開發詳解》

驅動開發和內核開發的聯系與區別

驅動是內核的一部分,驅動開發工程師所需的技能,和內核開發工程師相差無幾。但從工作內容來說,兩者還是有較大的差異。

驅動開發偏重于利用內核的現有驅動架構,給內核添加新的硬件支持,而內核開發,則主要是對系統架構進行修改,相當于為驅動開發提供彈藥。因此,從這個意義上來說,內核的開發更為困難,國內很少有這方面的人才。

永不返回的函數(never return function)

了解C語言的人都知道一個函數的最后一個語句通常是return語句。編譯器在處理返回語句時,除了將返回值保存起來之外,最重要的任務就是清理堆棧。具體來說,就是將參數以及局部變量從堆棧中彈出。然后再從堆棧中得到調用函數時的PC寄存器的值,并將其加一個指令的長度,從而得到下一條指令的地址。再將這個地址放入PC寄存器中,完成整個返回流程,接著程序就會繼續執行下去了。

對于返回值是void類型,也就是無返回值的函數,保存返回值是沒有意義的,但它仍然會執行清理堆棧的操作。

以上提到的這些,基本上適用于99.99%的場合。但凡事無絕對,在一些特殊的地方,例如操作系統內核中的某些函數,就不見得符合上邊所說的這些。永不返回的函數就是其中之一。

在Linux源代碼中,一個永不返回的函數通常擁有一個類似如下函數的聲明:

NORET_TYPE void do_exit(long code)

考慮到NORET_TYPE的定義:

#define NORET_TYPE /**/

因此,NORET_TYPE在這里僅僅起到方便閱讀代碼的作用,而并沒有什么其他的特殊作用。

看到do_exit函數,可能熟悉Linux內核的朋友已經猜出永不返回的函數和普通函數有什么區別了。沒錯,do_exit函數是銷毀進程的最后一步。由于進程已經銷毀,從進程堆棧中獲得下一條指令的地址就顯得沒有什么意義了。do_exit函數會調用schedule函數進行進程切換,從另一個進程的堆棧中獲得相關寄存器的值,并恢復那個進程的執行。因此do_exit函數在正常情況下是不會返回的,一個調用了do_exit函數的函數,其位于do_exit函數之后的語句是不會執行到的。因此那個函數也成為了永不返回的函數。

Linux鏈表實現

數據結構課本上教鏈表的時候,一般是這樣定義鏈表的數據結構的:

{% highlight c %}
typedef struct {
struct Node *next;
UserData data;
}Node;
{% endhighlight %}

其中,data字段包含了要保存到鏈表中的數據內容。使用這樣的數據結構實現的鏈表,通用性不好,需要針對不同的UserData類型定義不同的鏈表類型,盡管所有這些鏈表的操作都是類似的。當然這樣的定義在C++中不是太大的問題,使用模板就可以實現對不同UserData類型的處理,雖然這樣做無法避免代碼段的膨脹,但是僅就書寫使用來說,并沒有太大的不方便。

一種改進的辦法是將數據結構改為下面的樣子:

typedef struct {struct Node *next;void* data; }Node;

用無類型的指針指向需要保存的數據內容,是一個通用性不錯的辦法。但是C語言本身沒有對元數據的支持,一旦指針退化成無類型的指針,再想恢復成原來的數據類型就比較困難了。(元數據就是所有數據類型的基類,例如Java語言的Object類、MFC的CObject類、GTK的GObject結構。雖然元數據本身并不要求包含數據的類型信息,但在上述這些元數據的實現中,都提供了這個功能。)

Linux的做法是:(為了便于理解,進行了一些改寫,以忽略與本話題無關的部分)

typedef struct {struct Node *next; }Node; typedef struct {Node *node;UserDataActual data; }UserData;

這實際上是一種逆向思維,也就是將鏈表結點中包含用戶數據,改為用戶數據中包含鏈表結點。在鏈表處理時,將node傳給鏈表處理函數。而在引用用戶數據時,通過計算node和data的地址偏差,獲得data的實際地址。具體的技巧如下:

UserDataActual* p_data = (UserDataActual*)(((char*)node) - (int)(&(((UserData*)0)->node)) + (int)(&(((UserData*)0)->data)));

可以看出,這種實現方式對node在UserData中出現的位置也沒有什么額外的要求,有很好的靈活性。

同步鎖

read-write lock、RCU lock、spin lock

內核模塊的參數

用戶模塊可以通過main函數傳遞命令行參數。而內核模塊也有類似的用法:

insmod module.ko [param1=value param2=value ...]

為了使用這些參數的值,要在模塊中聲明變量來保存它們,并在所有函數之外的某個地方使用宏MODULE_PARM(variable, type)和MODULE_PARM_DESC(variable, description)來接收它們。

IO操作

readb 從 I/O 讀取 8 位數據 ( 1 字節 )

readw 從 I/O 讀取 16 位數據 ( 2 字節 )

readl 從 I/O 讀取 32 位數據 ( 4 字節 )

writeb(), writew(), writel()也是類似的。

IO操作之所以用宏實現,是由于這是和具體機器相關的操作,有的甚至要用到匯編來實現。從計算機體系結構來說,IO空間可以和內存空間屬于同一個地址空間,這樣就無需特殊的指令,直接使用C語言的賦值語句即可達到效果。IO空間也可以和內存空間采用不同的地址空間(比如x86就是這樣的),這時就需要特殊處理了。

內核模塊的條件編譯

內核代碼除了可以采用C語言的預處理命令,進行條件編譯之外。還可以在.o文件一級,實現條件編譯。

例如,在Kbuild系統的Makefile中:

obj-y += foo.o

該例子告訴Kbuild在這目錄里,有一個名為foo.o的目標文件。foo.o將從foo.c或foo.S文件編譯得到。obj-y表示編譯進內核,obj-m表示編譯成內核模塊。

將上面的例子稍微改一下:

obj-$(CONFIG_FOO) += foo.o

這里的$(CONFIG_FOO)可以為y(編譯進內核) 或m(編譯成模塊)。如果CONFIG_FOO不是y 和m,那么該文件就不會被編譯聯接了。通過控制$(CONFIG_FOO)的值,即可實現.o文件一級的條件編譯。

GPIO

GPIO相對來說是最簡單的一類驅動,代碼在drivers/gpio文件夾下。

從硬件來說,GPIO有兩種輸出模式:

push-pull模式電平轉換速度快,但是功耗相對會大些。

open-drain模式功耗低,且同時具有“線與”的功能。(同時注意GPIO硬件模塊內部是否有上拉電阻,如果沒有,需要硬件電路上添加額外的上拉電阻)

可在系統的/sys/class/gpio路徑下查看當前的GPIO設備。

參考文獻:

http://blog.csdn.net/mirkerson/article/details/8464290

http://www.cnblogs.com/lagujw/p/4226424.html

http://www.aichengxu.com/view/18529

http://wenku.baidu.com/view/a6c4b6bfc77da26925c5b001.html?re=view

從gpio_set_value到寄存器操作

include/linux/gpio.h: gpio_set_value

include/asm-generic/gpio.h: __gpio_set_value

drivers/gpio/gpiolib.c: gpiod_set_raw_value

這里會調用gpio_chip結構的set函數指針。我們只需要在定義gpio_chip結構的時候,將寄存器操作函數設置到set函數指針中即可。gpio_chip結構可在模塊初始化階段,使用gpiochip_add函數添加到系統中。

輸入事件處理

1.輪詢方式

drivers/input/keyboard/gpio_keys_polled.c中提供了輪詢方式的輸入事件處理。該實現主要是注冊了一個名為gpio-keys-polled的input_polled_dev。而input_polled_dev則利用queue_delayed_work實現任務的調度。

2.中斷方式

drivers/input/keyboard/gpio_keys_polled.c中提供了中斷方式的輸入事件處理。該實現最終調用了request_irq注冊中斷處理函數。

無論是輪詢還是中斷,處理的結果最終都會調用input_event函數生成應用層可訪問的事件。

代碼的編譯配置在:

Device Drivers ---> Input device support ---> Keyboards ---> <*> GPIO Buttons <*> Polled GPIO buttons

GPIO的使用方向

除了gpio_direction_input和gpio_direction_output之外,devm_gpio_request_one和gpio_request_one也可用于設置使用方向,只要設置好flag參數即可。內核中的leds-gpio和gpio-keys模塊都是使用后面的方法設置使用方向的。

此外,在board級的GPIO實現中,需要注意以下幾點:

1.是否有單獨的寄存器用于設置使用方向。這個問題與具體的硬件有關,有的硬件可根據賦值語句的方向,自動切換GPIO的使用方向。

2.如果有單獨的設置使用方向的寄存器的話,需要在gpio_set_value和gpio_get_value函數的實現中,將使用方向的設置操作添加進去。I2C的algo代碼并不會在set或著get操作時,修改GPIO的使用方向。

active_low

active_low的設置要根據硬件的連接,如果按下按鍵為高電平那么active_low=0,如果按下按鍵為低電平那么active_low=1.如果這個參數搞錯了,按鍵松開后就不斷發按鍵鍵碼,表現為屏幕上亂動作。

也因為active_low的存在,input_event返回的value實際上并不是GPIO的值,1表示按鍵按下,0表示按鍵抬起。

內核重啟

include/reboot.h里總有一個函數可以滿足你的需要。

總結

以上是生活随笔為你收集整理的linux内核研究(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。