驅動程序調試方法之printk――自制proc文件(一)
睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接
首先我們需要弄清楚proc機制,來看看fs/proc/proc_misc.c這個文件,從入口函數開始看:
proc_misc_init(void) #ifdef CONFIG_PRINTK { struct proc_dir_entry *entry; entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);//這里創建了一個proc入口kmsg if (entry) entry->proc_fops = &proc_kmsg_operations;//設置操作函數,見注釋1 } 注釋1: const struct file_operations proc_kmsg_operations = { .read = kmsg_read, .poll = kmsg_poll, .open = kmsg_open, .release = kmsg_release, }; 這個操作函數就用于多kmsg來進行操作 于是我們可以仿照來設計一下! #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <linux/proc_fs.h> struct proc_dir_entry *myentry; const struct file_operations proc_mymsg_operations = { }; static int mymsg_init(void) { myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root); if (myentry) myentry->proc_fops = &proc_mymsg_operations ; return 0; } void mymsg_eixt(void) { remove_proc_entry("mymsg", &proc_root); } module_init(mymsg_init); module_exit(mymsg_eixt); 這個函數只是在proc目錄下面創建了一個入口而已!我們加載后: ls /proc/mymsg -l 打印出如下信息: -r-------- 1 0 0 0 Feb 4 13:37 /proc/mymsg 如果我們想查看/proc/mymsg內容的話: # cat /proc/mymsg 打印出來 cat: read error: Invalid argument 這也是理所當然的,因為我們根本沒有讀函數嘛! 所以我們接下來要做的就是來完成這個讀函數,而這這個讀函數里面我們要做的就是將mylog_buf中的中的數據拷貝到用戶空間。有一個很關鍵的地方就是,我們的 mylog_buf 應該是一個環形隊列,關于環形隊列的概念我們先來說一下: front指向隊列首部,rear指向隊列尾部,size表示隊列長度。讀只能從首部讀,寫的話只能從尾部寫!寫到尾部的話,就回去重頭開始寫! 當front==rear的時候,可以判斷隊列是空的。 當(rear+1)/size==front的時候,可以判斷隊列是滿的。程序如下: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <linux/proc_fs.h> #define MYLOG_BUF_LEN 1024 struct proc_dir_entry *myentry; static char mylog_buf[MYLOG_BUF_LEN]; static char tmp_buf[MYLOG_BUF_LEN]; static int mylog_r = 0; //用來標識讀 static int mylog_w = 0; //用來標識寫 static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq); //判斷環形緩沖區是否為空 static int is_mylog_empty(void) { return (mylog_r == mylog_w); } //判斷環形緩沖區是否已滿 static int is_mylog_full(void) { return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r); } /*寫緩沖區:如果緩沖區已滿的話,就讓覆蓋掉下一個要讀的數據 *否則就直接寫入 此外在寫緩沖區函數里面還需要做的一件事情就是喚醒等待隊列, 這是因為當緩沖區為空的時候,如果調用讀函數的話,就會使進程 進入等待隊列,理當在寫入數據的時候喚醒進程 */ static void mylog_putc(char c) { if (is_mylog_full()) { /* 丟棄一個數據 */ mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN; } mylog_buf[mylog_w] = c; mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN; /* 喚醒等待數據的進程 */ wake_up_interruptible(&mymsg_waitq); /* 喚醒休眠的進程 */ } /*讀緩沖區:如果緩沖區為空的話,就返回0 否則從首部讀出一個數據,返回1 */ static int mylog_getc(char *p) { if (is_mylog_empty()) { return 0; } *p = mylog_buf[mylog_r]; mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN; return 1; } /*打印函數:這個函數是參考sprintf函數得編寫的 *它將傳遞進來的參數轉換為固定的格式之后,放入到一個臨時緩沖區里面 *然后將環形緩沖區的值寫入到mylog_buf緩沖區里面,詳見注釋2 */ int myprintk(const char *fmt, ...) { va_list args; int i; int j; va_start(args, fmt); i = vsnprintf(tmp_buf, INT_MAX, fmt, args);//將傳進來的參數轉換后放入tmp_buf va_end(args); for (j = 0; j < i; j++) mylog_putc(tmp_buf[j]);//將tmp_buf里面的東東放入mylog_buf緩沖區里面 return i; } /*讀函數:當在應用空間調用命令:cat /proc/mymsg的時候,會調用這個函數 * */ static ssize_t mymsg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int error = 0; int i = 0; char c; /* 把mylog_buf的數據copy_to_user, return */ //如果為非阻塞且mylog_buf為空,那么就出錯返回 if ((file->f_flags & O_NONBLOCK) && is_mylog_empty()) return -EAGAIN; //如果mylog_buf為空的話進程進入等待隊列,還記得我們在寫緩沖區 //函數里面會喚醒進程這件事情吧! error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty()); /* copy_to_user */ //首先從緩沖區里面獲得一個字符,然后拷貝到用戶空間 //如果緩沖區還有信息的話,就再次獲得字符,拷貝到用戶 //空間,直到緩沖區為空 while (!error && (mylog_getc(&c)) && i < count) { error = __put_user(c, buf);//將c的內容拷貝到用戶空間 buf++; i++; } if (!error) error = i; return error; } const struct file_operations proc_mymsg_operations = { .read = mymsg_read, }; static int mymsg_init(void) { myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root); if (myentry) myentry->proc_fops = &proc_mymsg_operations; return 0; } static void mymsg_exit(void) { remove_proc_entry("mymsg", &proc_root); } module_init(mymsg_init); module_exit(mymsg_exit); /*因為myprintk是我們自己寫的打印語句 *所以需要導出才能被使用,使用的時候還需要聲明一下: extern int myprintk(const char *fmt,...); */ EXPORT_SYMBOL(myprintk); MODULE_LICENSE("GPL"); 我們在來總結一下:在本文件里面我們做了兩件事情,一件事情是定義了一個寫函數,當我們在用戶空間使用命令:cat /proc/mymsg的時候,就會調用到這個讀函數,這個讀函數會將mylog_buf中的數據拷貝到用戶空間,那么mylog_buf里面的數據哪里來的呢?這就是我們做的另外一件事情,我們定義了一個打印函數,這個打印函數會將要打印的數據寫入一個臨時緩沖區,然后又從臨時緩沖區里面取出數據放入mylog_buf中。cat /proc/mymsg的候就會將mylog_buf中的數據拷貝到用戶空間,就可以顯示出來了! 注釋2: int sprintf(char * buf, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i=vsnprintf(buf, INT_MAX, fmt, args); va_end(args); return i; } 這個就是sprintf函數,它將傳遞進來的參數放入buf緩沖區,我們做的就是將這個緩沖區里面的值在放入mylog_buf緩沖區里面!相當的巧妙啊! 接下來我們不妨測試一下,測試函數如下: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> xtern int myprintk(const char *fmt, ...); static int first_drv_init(void) { myprintk("first_drv_init\n"); return 0; } static void first_drv_exit(void) { myprintk("abcdefhg\n"); } module_init(first_drv_init); module_exit(first_drv_exit); MODULE_LICENSE("GPL"); 先加載proc.ko 在加載test.ko cat /proc/mymsg,打印如下信息: first_drv_init 這正是我們打印的信息,成功了! RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成