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

歡迎訪問 生活随笔!

生活随笔

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

linux

linux内核研究(二)

發布時間:2023/12/20 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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->write@uartlite.c

這一層以下,就和具體的設備有關了。這里以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=>ulite_isr@uartlite.c

這里仍以Xlinux的uartlite為例。初始化階段注冊ulite_isr中斷服務程序。

2.ulite_receive@uartlite.c

具體的寄存器操作。

3.tty_flip_buffer_push@tty_buffer.c

4.tty_schedule_flip@tty_buffer.c

調用schedule_work喚醒上層應用。

select代碼分析

1.select@select.c

2.core_sys_select@select.c

3.do_select@select.c

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既然由若干模塊組成,那么這些模塊在啟動階段,必然存在一個加載順序的問題。這方面可以通過以下的宏來確定加載的優先級。

#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。

總結

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

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