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

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

  睡了個好覺,很晚才起,很久沒有這麼舒服過了,今天的任務不重,因此壓力不大,呵呵,如今的天氣真的好冷,不過實驗室有空調,我仍是喜歡待在這裏,有一種不同的感受,在寫了這麼多天以後,本身有些不懂的頁漸漸的豁然開朗了嗎,並且也交到了一些朋友,真是至關開心啊。今天將介紹一下向內核中添加代碼,一塊兒來看看吧~node

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

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()帶來的額外開銷。除了經過設備驅動程序向內核添加代碼以外,還有別的方式 的,用戶空間能夠經過系統調用來訪問內核服務程序和系統硬件,這裏很少闡釋,都知道有這回事就好了。app

 

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

#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

相關文章
相關標籤/搜索