說點上節沒有講完的話題
使用者地址檢測 簡單模組調試 以及一些雜項檢測使用者空間地址的有效性
上一節中提到在read write時候要檢測使用者空間傳遞的參數地址是否是有效地址,有的核心功能會自行檢測,但是在調用輕量級的核心功能時候,就可能不去檢測使用者空間的地址是否有效,如果此時使用者無傳遞一個無效地址,而核心功能去操作了它,這時棘手的問題出現了,輕則核心oops 關機重啟就OK了,在特別嚴重的情況下,可能你的系統就崩潰了(我又遇到過),所以,我們在驅動程式中操作使用者空間地址時候要小心加小心。如果電腦配置可以就在虛擬機器中玩, 或者在開發板上試,當然這邊的測試代碼我都有試過,不至於讓你系統崩潰的。
如何檢測呢?
調用一個access_ok函數去檢測
#define access_ok(type,addr,size)
type 標識讀寫操作VERIFY_READ表示地址可讀,VERIFY_WRITE表示地址可寫
addr 使用者傳入的地址
size 讀寫的長度
此代碼在有記憶體管理的晶片與無記憶體管理之間有區別
我們 看一段核心代碼 (path : arch/arm/include/asm/uaccess.h)
#define access_ok(type,addr,size)(__range_ok(addr,size) == 0)#ifdef CONFIG_MMU...#define __range_ok(addr,size) ({ \unsigned long flag, roksum; \__chk_user_ptr(addr);\__asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \: "=&r" (flag), "=&r" (roksum) \: "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \: "cc"); \flag; })#else...#define __range_ok(addr,size)(0)
即在有記憶體管理並配置了記憶體管理的晶片內調用次函數會執行檢測操作,而在沒有配置記憶體管理的晶片中此函數總是返回真,而做驅動的不應該做這些假設,所以傳入的參數在有必要的情況下還是要自行檢測再看看copy_to_user函數
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n){if (access_ok(VERIFY_WRITE, to, n))n = __copy_to_user(to, from, n);return n;}
可以看到他在函數內部做了這種檢測
而當我們調用
__copy_to_user__copy_from_userget_user__get_userput_user__put_user
時都需要檢測使用者地址是否可用
簡單模組調試技術
為什麼要加簡單呢? 因為這邊只介紹了用列印來偵錯工具。
看了LDD3上邊介紹的很多調試技術 查詢調試 觀察調試之類
我覺得 列印調試來的最簡單最直接 雖然他有一些限制
1、大量的使用printk會使系統變慢
2、沒次列印一行都會引起磁碟操作
...
在printk中有7中 訊息的選項 表示著不同的訊息等級
| KERN_GMERG<0> |
用於緊急訊息, 常常是那些崩潰前的訊息. |
| KERN_ALERT<1> |
需要立刻動作的情形. |
| KERN_CRIT<2> |
嚴重情況, 常常與嚴重的硬體或者軟體失效有關. |
| KERN_ERR<3> |
用來報告錯誤情況; 裝置驅動常常使用 來報告硬體故障. |
| KERN_WARNING<4> |
有問題的情況的警告, 這些情況自己不會引起系統的嚴重問題 |
| KERN_NOTICE<5> |
正常情況, 但是仍然值得注意. 在這個層級一些安全相關的情況會報告. |
| KERN_INFO<6> |
資訊型訊息. 比如 :列印它們發現的硬體的資訊. |
| KERN_DEBUG<7> |
用作調試訊息. |
核心中定義了DEFAULT_MESSAGE_LOGLEVEL(在printk.c中)預設數值小於它的訊息類型才會被答應到終端,我們可以把他設定為8則所有的資訊都會被終端列印出來。
在系統中我們 可以使用 echo 8 > /proc/sys/kernel/printk 來調整這個數值(要root許可權) 使資訊全部被列印出來。
當然我們 也可以通過dmesg來查看所有的列印資訊(有一點不適用,就是當系統出現oops的時候 就不行了 因為你已經死機了 也就輸不了這個命令 就看不到列印資訊了)。
#if SIMPLE_DEBUG#define D(...) printk(KERN_DEBUG __VA_ARGS__)#define WAR(...) printk(KERN_WARNING __VA_ARGS__)#else#define D(...) ((void)0)#define WAR(...) ((void)0)#endif
在需要調試的時候,我們去定義SIMPLE_DEBUG這個宏,在驅動代碼測試都OK可以發行時候,去掉這個定義。
在需要列印的地方我們就使用
D(“print the log int func:%s line:%d”, __func__ ,__LINE__);
當然要修改成有意義的debug資訊
列印當前進程資訊
核心模組不像應用程式一樣順序執行,只用應用進程調用到想關聯的函數才會到核心模組中call這個介面,那可不可以 列印調用進程的資訊呢?
答案是肯定的,linux中定義了current這個變數(<linux/sched.h>)current指向了當前的進程,他是一個task_struct類型
其中有兩個重要的成員
comm 表示了 當前的命令名名
pid 表示了當前進程號
D("[process: %s] [pid: %d] xxx\n" , current->comm, current->pid);
核心的container_of函數
在寫字元裝置驅動時候我們都會去自訂一個結構,其中包含了cdev結構
struct simple_dev{char *data;loff_t count;struct cdev cdev;struct semaphore semp;};
在open方法中除了上一節看到的那樣用一個次裝置好來擷取結構(有時候會出錯 如果我們次裝置號不是從0開始分配)
所以需要尋求一種安全的方法
container_of為我們提供了這麼一個很好的介面
/** * container_of - cast a member of a structure out to the containing structure * @ptr:the pointer to the member. * @type:the type of the container struct this is embedded in. * @member:the name of the member within the struct. * */#define container_of(ptr, type, member)
再解釋一下
ptr是指結構體一個成員的地址
type 指要獲得的結構體
member ptr指向的成員在結構體中的名字
/*container_of(pointer, container_type, container_fild) we can get the container_type with one number in the container by this function. container_fild is the one of the number in the container_type , pointer point to confain_field type */temp_dev = container_of(inodp->i_cdev, struct simple_dev, cdev);
上邊的E文 是我寫的 poor English
container_of 的實現基本原理是這樣的:
知道了結構體中某個成員的地址, 又可以求的該成員在改結構體中得位移,拿成員的地址減去這個位移,就得到了整個結構的地址,太佩服寫核心的人了
#define container_of(ptr, type, member) ({\const typeof(((type *)0)->member) * __mptr = (ptr);\(type *)((char *)__mptr - offsetof(type, member)); })