最近一段時間在研究 linux 的核心,linux 核心中的代碼十分的精煉,無論出于什麼目的來學習它,都會有很多的收獲。
但是,看代碼與寫代碼雖說都需要大量的腦力參與其中,但是,二者的等級絕對不同。怎麼各不同?就相當于我們在
學習的時候,知道一個函數應該如何的使用 與 這個函數的内部結構是什麼樣的,在不參照其他人代碼的情況下自己
編寫相同功能的函數應該如何實作一樣。
是以,對于學習核心的方法是這樣的:
首先,将核心中實作的方法/函數進行封裝,寫到自己的程式中,然後通過不同參數的
傳遞來慢慢熟悉這個函數的功能是什麼樣的。(知其然)
然後,在仔細的閱讀所熟悉功能的函數内部實作中是怎樣處理
如此之多的功能的。(知其是以然)
但是,這兩個過程确實等同重要的,也就是說不能夠從第一個階段直接跨越到第二個階段,也不能夠将認知僅僅停留在第一個階段。
========================================================
本篇文章主要是為今後進行第一個階段進行一些鋪墊,主要介紹的就是,
如何在自己的程式中使用核心中實作的庫函數,在這裡僅僅舉一個簡單的小例子來說明。
實驗目的:
在自己編寫的程式中引用核心中的庫函數
實驗環境:
gcc 4.4.7,
linux -centos
實驗思想:
通過 Makefile 和 導出符号表的方式來将核心子產品動态的加載到使用者自行編譯的程式中,
實質上是使用類似于驅動子產品的形式加載核心的方法。
也就是根據使用者編寫的程式需要來動态的将核心中的實作代碼抽取出來進行編譯成目标檔案。
實驗代碼:
實驗代碼主要分為3個部分: hello.c myFunc.h myFunc.c
和一個 Makefile
其中 hello.c 中會調用 myFunc.c 中所實作的函數 void sayHello ( void )
并且為了表明,在執行上述操作之後真的可以在目前使用者自行編寫的程式中使用kernel 中的變量,
我們在程式中定義了一個位于 include/linux/list.h 中所定義的 struct list_head 變量,它也将會是
後續文章中主要介紹的對象,是以在這裡先做一下鋪墊與測試。
在 myFunc.h myFunc.c 中分别是 sayHello 方法的定義與實作,在這裡之是以寫上子函數調用
是為了學習在後續編寫複雜調用的時候,主方法是如何對位于其他檔案中的子函數的編譯等處理問題。
//hello.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>
#include "myFunc.h"
static int __init hello_init ( void )
{
printk(KERN_EMERG"hello linux internal world \n") ;
sayHello( ) ;
struct list_head myList ;//no error ,when compiling
return 0 ;
}
static void __exit hello_exit ( void )
{
printk(KERN_EMERG"bye ,see you \n") ;
}
module_init (hello_init) ;
module_exit (hello_exit) ;
MODULE_LICENSE("GPL") ;
MODULE_AUTHOR("inuyasha1027") ;
MODULE_VERSION("1.0") ;
//myFunc.h
#ifndef __MYFUNC_H__
#define __MYFUNC_H__
void sayHello(void) ;
#endif
//myFunc.c
#include <linux/module.h>
void sayHello ( void )
{
printk(KERN_EMERG"emm hi , i am inuyasha \n nice to meet you !" ) ;
}
EXPORT_SYMBOL(sayHello) ;
MODULE_LICENSE("GPL") ;~
//Makefile
obj-m := hello.o myFunc.o
all:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
下面介紹的是在代碼編寫之後,如何對其進行編譯與運作
首先,makefile 已經編寫好了,那麼我們來輸入 make 來進行編譯

編譯好了之後,我們将會在目前目錄下面看到有許多的目标檔案,其中我們所需要的就是 1. hello.ko 2.myFunc.ko
我們所熟知的有 .so ----》shared object ,是linux 中的共享庫,同樣也被稱作動态庫。
是以對于 .ko 檔案來說,從其英文名稱上來入手,ko ===> kernel object ,也就是核心子產品的意思,
它可以動态地加載到系統中運作的核心中,也可以動态地從其中(運作的核心)進行解除安裝。
那麼我們就來對其進行加載以下吧,對于将 .ko 檔案加載到核心的指令是 insmod ,
而将 .ko 檔案從核心中解除安裝的指令是 rmmod ,不過加載和解除安裝的順序是有一定要求的。 由前面的代碼實作可知,
hello.c 中調用了 myFunc.c 中實作的函數,那麼在編譯之後所生成的 hello.ko 檔案中一定會依賴 myFunc.ko 。
是以,應該首先加載 myFunc.ko , 然後在加載 hello.ko 才可以得到正确的輸出。
同樣,如何知道某一個核心子產品 *.ko 已經被核心所加載了呢? 查詢的指令有很多種,其中比較常用的有
1. ps -ef | grep myFunc
2. cat /proc/kallsyms | grep myFunc
3. lsmod | grep myFunc
任何一種方法都可以, 因為我們并沒有在 myFunc 中寫入在運作的時候輸出的提示資訊,是以需要通過
系統指令來檢視它是否在運作,以及運作狀态相關的資訊。
接下來,我們使用同樣的方法,在 myFunc 被加載到核心中的基礎之上,将 hello.ko 加載到核心中
通過再次輸入指令 lsmod | grep myFunc 可以看到 hello 這個子產品已經類似于挂載的關系依附于其之上,這也是為什麼
在将 myFunc hello 核心子產品從核心中解除安裝下來的時候,需要首先将 hello 子產品解除安裝下來,然後在解除安裝 myFunc 的原因。
實驗不足與改進:
這個實驗中有一個地方和我預先想到的輸出有所不同,在 hello 中列印資訊的時候多加了一個換行符号
\n 沒有想到資訊竟然沒有一次性全部輸出,這個可能與 printk 函數的屬性有關,也就是說 作為 printf
的兄弟的printk 在每次向螢幕輸出的時候,是需要向核心請求一個緩沖區來存放将要輸出的内容的,
這樣長長的一句話可能由于配置設定的緩沖區的不同而被多次輸出,而核心或許将 \n 這一換行符号,默許是
配置設定緩沖區結束的一個标志,這樣後半句話就會在前一句輸出之後,清空緩沖區,然後裝載後一句話,
然後将後一句話輸出,這才導緻了,換行符号的後半句話在将 hello 子產品從核心中解除安裝下來之後才顯示
到螢幕上面的原因吧。(經過後續的實驗證明,這個想法是錯誤的)
不過具體的原因還不是很清楚,但是之是以知道它是錯的是因為:在執行 insmod *.ko
子產品加載的時候,所加載的僅僅是零零散散的 目标檔案,而非最終我們想要實作全局功能的hello.o
真正最終起到全局功能的就是 hello.o , 也就是通過 ismod hello.o 可以将全部的資訊(無論是否有 \n 等等)
不過它的運作之前,是必須要通過 ismod myFunc.ko ismod hello.ko 這些核心加載指令的基礎之上才能夠正确運作的。
printk 中還需要知道一點就是,在輸出的字元串資訊前面中是需要加上各種核心中定義的宏變量所代表輸出資訊的不同等級的,
如果等級過低則不會輸出,那麼在控制台上看不到寫入方法中本應顯示的資訊的。 這裡我們使用的是最高等級,
緊急狀态 : KERN_EMERG 就可以在螢幕上看到寫入程式中的字元了。
後續實驗主要是圍繞 list hlist