24小時學通Linux內核之向內核添加代碼


 

 

 

24小時學通Linux內核之向內核添加代碼

  睡了個好覺,很晚才起,好久沒有這么舒服過了,今天的任務不重,所以壓力不大,呵呵,現在的天氣真的好冷,不過實驗室有空調,我還是喜歡待在這里,有一種不一樣的感覺,在寫了這么多天之后,自己有些不懂的頁漸漸的豁然開朗了嗎,而且也交到了一些朋友,真是相當開心啊。今天將介紹一下向內核中添加代碼,一起來看看吧~

  先來熟悉一下文件系統,通過/dev可以訪問Linux的設備,我們以men設備驅動程序為例來看看隨機數是如何產生的,源代碼在dirvers/char/mem.c上可以查看

static int memory_open(struct inode * inode * inode,struct file * filp) { switch (iminor(inode)) {  //switch語句根據從設備號來初始化驅動程序的數據結構 case 1: ... case 8: filp->f_op = &random_fops; break; case 9: filp->f_op = &urandom_fops; break;

  那么上述程序的filps和fop是什么呢?實際上filp只是一個文件結構指針,而fop是一個file_operations結構指針,內核通過file_operations結構來確定操作文件時要調用的函數,下面的file_operations結構用於隨機設備驅動的部分內容,代碼在include/linux/fs.h上可以查看到:

struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; ... struct address_space *f_mapping; };

  q驅動程序所實現的函數必須符合file_operations結構中所列出的函數原型,代碼在dirvers/char/random.c上可以查看:

struct file_operations random_fops = { .read = random_read, .write = random_write, .poll = random_poll,  //poll操作允許某種操作之前查看該操作是否阻塞 .ioctl = random_ioctl, };  //隨機設備提供的操作有以上 struct file_operations urandom_fops = { .read = random_read, .write = random_write, .ioctl = random_ioctl, }; //urandom設備提供的操作有以上

  如果設備驅動程序在內核空間運行,但是緩沖區卻位於用戶空間,那我們該如何才能安全訪問buf中的數據呢,下面來說下數據在用戶空間和內核空間之間的奧秘,Linux提供的copy_to_user()和copy_from_user()使得驅動程序可以在內核空間和用戶空間上傳遞數據,在read_random()中,通過extract_entropy()函數來實現這個功能,下面代碼在dirvers/char/random.c上可以查看(下面的代碼沒有敲完,主要是不是很懂,望大神指教)

static ssize_t extract_entropy(struct entract_syore *r,void *buf,size_t nbytes,int flags) { ... { static ssize_t extract_entropy(struct entropy_store *r,void *buf,size_t nbytes,int flags) { ...

  內核空間和用戶空間的程序可能都需要使用已經獲得的隨機數,內核空間的程序可以通過不設置標志位來避免函數copyto_user()帶來的額外開銷。除了通過設備驅動程序向內核添加代碼之外,還有別的方式 的,用戶空間可以通過系統調用來訪問內核服務程序和系統硬件,這里不多闡釋,都知道有這回事就行了。

 

  下面我們來介紹怎么去編寫源代碼,當我們去編寫一個復雜的設備驅動程序時,也許要輸出驅動程序中定義的某些符合,以便讓內核其它模塊使用,這些通常被用在低級的驅動程序中,以便根據這些基本的函數來構建更高級的驅動程序,在Linux2.6內核中,code monkey可以用如下兩個宏輸出符號,代碼在include/linux/module.h中查看:

#define EXPORT_SYMBOL(sym) __EXPORT_SYMBOL(sym, ""#define EXPORT_SYMBOL_GPL(sym) __EXPORT_SYMBOL(sym, "_gpl")

  目前為止,我們介紹的設備 驅動程序都是主動操作,或者對設備的數據進行讀寫操作,那么它的功能不止這些的時候會怎么樣呢?在Linux中,設備驅動程序解決這些問題的典型方式就是使用ioctl。ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。

調用個數如下:

int ioctl(int fd, ind cmd, …);

其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設備的控制命令,至於后面的省略號,那是一些補充參數,一般最多一個,有或沒有是和cmd的意義相關的。ioctl函數是文件結構中的一個屬性分量,就是說如果你的驅動程序提供了對ioctl的支持,用戶就可以在用戶程序中使用ioctl函數控制設備的I/O通道。

 

ioctl命令號:

dir:

  代表數據傳輸的方向,占2位,可以是_IOC_NONE(無數據傳輸,0U),_IOC_WRITE(向設備寫數據,1U)或_IOC_READ(從設備讀數據,2U)或他們的邏輯或組合,當然只有_IOC_WRITE和_IOC_READ的邏輯或才有意義。

type:

描述了ioctl命令的類型,8位。每種設備或系統都可以指定自己的一個類型號,ioctl用這個類型來表示ioctl命令所屬的設備或驅動。一般用ASCII碼字符來表示,如 'a'。

  nr:

ioctl命令序號,一般8位。對於一個指定的設備驅動,可以對它的ioctl命令做一個順序編碼,一般從零開始,這個編碼就是ioctl命令的序號。

  size:

ioctl命令的參數大小,一般14位。ioctl命令號的這個數據成員不是強制使用的,你可以不使用它,但是我們建議你指定這個數據成員,通過它我們可以檢查用戶空間數據的大小以避免錯誤的數據操作,也可以實現兼容舊版本的ioctl命令。

 

ioctl返回值:

   ioctl函數的返回值是一個整數類型的值,如果命令執行成功,ioctl返回零,如果出現錯誤,ioctl函數應該返回一個負值。這個負值會作為errno值反饋給調用此ioctl的用戶空間程序。關於返回值的具體含義,請參考<linux/errno.h>和<asm/errno.h>頭文件。

 

ioctl參數:

  首先要說明這個參數是有用戶空間的程序傳遞過來的,因此這個指針指向的地址是用戶空間地址,在Linux中,用戶空間地址是一個虛擬地址,在內核空間是無法直接使用它的。為了解決在內核空間使用用戶空間地址的數據,Linux內核提供了以下函數,它們用於在內核空間訪問用戶空間的數據,定義在<asm/uaccess.h>頭文件中:

unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n); unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);

copy_from_user和copy_to_user一般用於復雜的或大數據交換,對於簡單的數據類型,如int或char,內核提供了簡單的宏來實現這個功能:

#define get_user(x,ptr)
#define put_user(x,ptr)//x是內核空間的簡單數據類型地址,ptr是用戶空間地址指針。

  

cmd參數如何得出:

  一個cmd參數被分為4段,每段都有其特殊的含義,cmd參數在用戶程序端由一些宏根據設備類型、序列號、傳送方向、數據尺寸等生成,這個整數通過系統調用傳遞到內核中的驅動程序,再由驅動程序使用解碼宏從這個整數中得到設備的類型、序列號、傳送方向、數據尺寸等信息,然后通過switch{case}結構進行相應的操作。解釋一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt這兩個文檔有說明的 。

1)幻數:說得再好聽的名字也只不過是個0~0xff的數,占8bit(_IOC_TYPEBITS)。這個數是用來區分不同的驅動的,像設備號申請的時候一樣,內核有一個文檔給出一些推薦的或者已經被使用的幻數

2)序數:用這個數來給自己的命令編號,占8bit(_IOC_NRBITS),我的程序從1開始排序。

 

3)數據傳輸方向:占2bit(_IOC_DIRBITS)。如果涉及到要傳參,內核要求描述一下傳輸的方向,傳輸的方向是以應用層的角度來描述的。

  • _IOC_NONE:值為0,無數據傳輸。
  • _IOC_READ:值為1,從設備驅動讀取數據。
  • _IOC_WRITE:值為2,往設備驅動寫入數據。
  • _IOC_READ|_IOC_WRITE:雙向數據傳輸。

4)數據大小:與體系結構相關,ARM下占14bit(_IOC_SIZEBITS),如果數據是int,內核給這個賦的值就是sizeof(int)。

 

 ioctl如何實現:

  在驅動程序中實現的ioctl函數體內,實際上是有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎么實現這些操作,這是每一個程序員自己的事情,因為設備都是特定的,這里也沒法說,關鍵在於怎么樣組織命令碼,因為在ioctl中命令碼是唯一聯系用戶程序命令和驅動程序支持的途徑。

  命令碼的組織是有一些講究的,因為我們一定要做到命令和設備是一一對應的,這樣才不會將正確的命令發給錯誤的設備,或者是把錯誤的命令發給正確的設備,或者是把錯誤的命令發給錯誤的設備。這些錯誤都會導致不可預料的事情發生,而當程序員發現了這些奇怪的事情的時候,再來調試程序查找錯誤,那將是非常困難的事情

所以在Linux核心中是這樣定義一個命令碼的:

____________________________________

| 設備類型 | 序列號 | 方向 |數據尺寸|

|----------|--------|------|--------|

| 8 bit    |  8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

  這樣一來,一個命令就變成了一個整數形式的命令碼。但是命令碼非常的不直觀,所以Linux Kernel中提供了一些宏,這些宏可根據便於理解的字符串生成命令碼,或者是從命令碼得到一些用戶可以理解的字符串以標明這個命令對應的設備類型、設備序列號、數據傳送方向和數據傳輸尺寸。

 

  在內核中是無法直接訪問用戶空間地址數據的。因此凡是從用戶空間傳遞過來的指針數據,務必使用內核提供的函數來訪問它們。這里有必要再一次強調的是,在內核模塊或驅動程序的編寫中,我們強烈建議你使用內核提供的接口來生成並操作ioctl命令號,這樣可以對命令號賦予特定的含義,使我們的程序更加的健壯;另一方面也可以提高程序的可移植性。

 

  最后我們來介紹一下添加代碼后的編譯和調試,在內核中添加代碼后就需要不斷運行,修復錯誤,我們知道當對/proc文件系統進行讀寫操作時,它的每一個結點都鏈接到一個內核函數,在Linux2.6內核中,要想你的設備能夠被訪問,首先就要在/proc文件系統中創建一個入口,這個可以通過creat_proc_read_entry()來實現,代碼在include/linux/proc_fs.h上查看:

static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
    mode_t mode,struct proc_dir_entry *base,
    read_proc_t *read_proc,void * data)

*name是結點在/proc文件系統的入口,*base指向設置proc文件的目標路徑,如果它的值為NULL,表示該文件就在/proc目錄下,讀取該文件可以調用*read_proc指向的函數。這里也不多加闡釋了,整個也是很簡單的過程。

  小結

  今天的重點是iotcl函數了,其中還有很多向內核中添加代碼的細節沒有講到,主要是這些都涉及到過多的操作,需要大家多看源代碼並且多動手在Linux上操作才能完全掌握,,今天寫的一些也借鑒了一些大牛的文章,總之 收獲很多,最后幾天了,真的是很開心啦,和大家一起分享真的很快樂的~~

 

  版權所有,轉載請注明轉載地址:http://www.cnblogs.com/lihuidashen/p/4255826.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM