在上章34.Linux-printk分析、使用printk調試驅動里講述了: printk()會將列印信息存在內核的環形緩衝區log_buf[]里, 可以通過dmesg命令來查看log_buf[] 1.環形緩衝區log_buf[]又是存在內核的哪個文件呢? 位於/proc/kmsg里,所以除了dme ...
在上章34.Linux-printk分析、使用printk調試驅動里講述了:
printk()會將列印信息存在內核的環形緩衝區log_buf[]里, 可以通過dmesg命令來查看log_buf[]
1.環形緩衝區log_buf[]又是存在內核的哪個文件呢?
位於/proc/kmsg里,所以除了dmesg命令查看,也可以使用cat /proc/kmsg來查看
2.但是,dmesg命令和cat /proc/kmsg有所不同
2.1 dmesg命令
每次使用,都會列印出環形緩衝區的所有信息
2.2 cat /proc/kmsg
只會列印出每次新的環形緩衝區的信息
比如,第一次使用cat /proc/kmsg,會列印出內核啟動的所有信息
第二次使用cat /proc/kmsg,就不會出現之前列印的信息,只列印繼上次使用cat /proc/kmsg之後的新的信息,比如下圖所示:
3.接下來我們便進入內核,找/proc/kmsg文件在哪生成的
搜索"kmsg",找到位於fs\proc\proc_misc.c 文件的proc_misc_init()函數中,
該函數主要用來生成登記的設備文件,具體代碼如下所示:
const struct file_operations proc_kmsg_operations = { .read = kmsg_read, //讀函數 .poll = kmsg_poll, .open = kmsg_open, .release = kmsg_release, }; void __init proc_misc_init(void) { ... ... struct proc_dir_entry *entry; // 用來描述文件的結構體, entry = create_proc_entry("kmsg", S_IRUSR, &proc_root); //使用create_proc_entry()創建文件 if (entry) entry->proc_fops = &proc_kmsg_operations; //對創建的文件賦入file_ operations ... ... }
從上面代碼得出,/proc/kmsg文件,也是有file_operations結構體的,而cat命令就會一直讀/proc/kmsg的file_operations->read(),實現讀log_buf[]的數據
且/proc/kmsg文件是通過create_proc_entry()創建出來的,參數如下所示:
"kmsg":文件名
&proc_root:父目錄,表示存在/proc根目錄下
S_IRUSR: 等於400,表示擁有者(usr)可讀,其他任何人不能進行任何操作,如下圖所示:
該參數和chmod命令參數一樣,除了S_IRUSR還有很多參數,比如:
S_IRWXU: 等於700, 表示擁有者(usr)可讀(r)可寫(w)可執行(x)
S_IRWXG: 等於070, 表示擁有者和組用戶 (group)可讀(r)可寫(w)可執行(x)
4.為什麼使用dmesg命令和cat /proc/kmsg會有這麼大的區別?
我們進入proc_kmsg_operations-> kmsg_read()看看,就知道了
static ssize_t kmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos) { /*若在非阻塞訪問,且沒有讀的數據,則立刻return*/ if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0)) return -EAGAIN; return do_syslog(2, buf, count); //開始讀數據,buf:用戶層地址,count:要讀的數據長度 }
5.proc_kmsg_operations-> kmsg_read()->do_syslog(9, NULL, 0)的內容如下所示:
其中log_start和log_end就是環形緩衝區的兩個標誌, log_start也可以稱為讀標誌位, log_end也可以稱為寫標誌位,當寫標誌和讀標誌一致時,則表示沒有讀的數據了。
6.proc_kmsg_operations-> kmsg_read()->do_syslog(2, buf, count)的內容如下所示:
case 2: /* Read from log */ error = -EINVAL; if (!buf || len < 0) //判斷用戶層是否為空,以及讀數據長度 goto out; error = 0; if (!len) goto out; if (!access_ok(VERIFY_WRITE, buf, len)) { // access_ok:檢查用戶層地址是否訪問OK error = -EFAULT; goto out; } /*若沒有讀的數據,則進入等待隊列*/ error = wait_event_interruptible(log_wait, (log_start - log_end)); if (error) goto out; i = 0; spin_lock_irq(&logbuf_lock); while (!error && (log_start != log_end) && i < len) { c = LOG_BUF(log_start); // LOG_BUF:取環形緩衝區log_buf[]里的某個位置的數據 log_start++; //讀地址++ spin_unlock_irq(&logbuf_lock); error = __put_user(c,buf); //和 copy_to_user()函數一樣,都是上傳用戶數據 buf++; //用戶地址++ i++; //讀數據長度++ cond_resched(); spin_lock_irq(&logbuf_lock); } spin_unlock_irq(&logbuf_lock); if (!error) error = i; break;} out: return error; }
顯然就是對環形緩衝區的讀操作,而環形緩衝區的原理又是什麼?
7.接下來便來分析環形緩衝區的原理
和上面函數一樣, 環形緩衝區需要一個全局數組,還需要兩個標誌:讀標誌R、寫標誌W
我們以一個全局數組my_buff[7]為例,來分析:
7.1環形緩衝區初始時:
int R=0; //記錄讀的位置 int W=0; //記錄寫的位置
上面的代碼,如下圖1所示:
R:從數組[R]開始讀數據
W:從數組[W]開始寫數據
所以,當R==W時,則表示沒有數據可讀,通過這個邏輯便能寫出讀數據了
7.2當我們需要讀數據時:
int read_buff(char *p) //p:指向要讀出的地址 { if(R==W) return 0; //讀失敗 *p=my_buff[R]; R=(R+1)%7; //R++ return 1; //讀成功 }
我們以W=3,R=0,為例,調用3次read_buff()函數,如下圖所示:
讀數據完成,剩下就是寫數據了,很顯然每寫一個數據,W則++
7.3所以寫數據函數為:
void write_buff(char c) //c:等於要寫入的內容 { my_buff [W]=c; W=(W+1)%7; //W++ if(W==R) R=(R+1)%7; //R++ }
7.3.1 上面的代碼,為什麼要判斷if((W==R)?
比如,當我們寫入一個8個數據,而my_buff[]只能存7個數據,必定會有W==R的時候,如果不加該判斷,效果圖如下所示:
然後我們再多次調用read_buff(),就會發現只讀的出第8個數據的值,而前面7個數據都會被遺棄掉
7.3.2 而加入判斷後,效果圖如下所示:
然後我們再多次調用read_buff(),就可以讀出my_buff [2]~ my_buff [0]共6個數據出來
總結:
由於read_buff()後,R都會+1,所以每次 cat /proc/kmsg , 都會清空上次的列印信息。
8.環形緩衝區分析完畢後,我們就可以直接來寫一個驅動,模仿/proc/kmsg文件來看看
流程如下:
- 1)定義全局數組my_buff[1000]環形緩衝區,R標誌,W標誌,然後提供寫函數,讀函數
- 2)自製一個myprintk(),通過傳入的數據來放入到my_buff[]環形緩衝區中
- (PS:需要通過EXPORT_SYMBOL(myprintk)聲明該myprintk,否則不能被其它驅動程式調用 )
- 3)寫入口函數
- ->3.1) 通過create_proc_entry()創建/proc/mykmsg文件
- ->3.2 )並向mykmsg文件里添加file_operations結構體
- 4)寫出口函數
- ->4.1) 通過remove_proc_entry()卸載/proc/mykmsg文件
- 5)寫file_operations->read()函數
- ->5.1) 仿照/proc/kmsg的read()函數,來讀my_buff[]環形緩衝區的數據
具體代碼如下所示:
#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 my_buff_len 1000 //環形緩衝區長度 static struct proc_dir_entry *my_entry; /* 聲明等待隊列類型中斷 mybuff_wait */ static DECLARE_WAIT_QUEUE_HEAD(mybuff_wait); static char my_buff[my_buff_len]; unsigned long R=0; //記錄讀的位置 unsigned long W=0; //記錄寫的位置 int read_buff(char *p) //p:指向要讀出的地址 { if(R==W) return 0; //讀失敗 *p=my_buff[R]; R=(R+1)%my_buff_len; //R++ return 1; //讀成功 } void write_buff(char c) //c:等於要寫入的內容 { my_buff [W]=c; W=(W+1)%my_buff_len; //W++ if(W==R) R=(R+1)%my_buff_len; //R++ wake_up_interruptible(&mybuff_wait); //喚醒隊列,因為R != W } /*列印到my_buff[]環形緩衝區中*/ int myprintk(const char *fmt, ...) { va_list args; int i,len; static char temporary_buff[my_buff_len]; //臨時緩衝區 va_start(args, fmt); len=vsnprintf(temporary_buff, INT_MAX, fmt, args); va_end(args); /*將臨時緩衝區放入環形緩衝區中*/ for(i=0;i<len;i++) { write_buff(temporary_buff[i]); } return len; } static int mykmsg_open(struct inode *inode, struct file *file) { return 0; } static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos) { int error = 0,i=0; char c; if((file->f_flags&O_NONBLOCK)&&(R==W)) //非阻塞情況下,且沒有數據可讀 return -EAGAIN;
error = -EINVAL;
if (!buf || !count ) goto out;
error = wait_event_interruptible(mybuff_wait,(W!=R)); if (error) goto out;
while (!error && (read_buff(&c)) && i < count) { error = __put_user(c,buf); //上傳用戶數據 buf ++; i++; } if (!error) error = i; out: return error; } const struct file_operations mykmsg_ops = { .read = mykmsg_read, .open = mykmsg_open, }; static int mykmsg_init(void) { my_entry = create_proc_entry("mykmsg", S_IRUSR, &proc_root); if (my_entry) my_entry->proc_fops = &mykmsg_ops; return 0; } static void mykmsg_exit(void) { remove_proc_entry("mykmsg", &proc_root); } module_init(mykmsg_init); module_exit(mykmsg_exit); EXPORT_SYMBOL(myprintk); MODULE_LICENSE("GPL");
PS:當其它驅動向使用myprintk()列印函數,還需要在文件中聲明,才行:
extern int myprintk(const char *fmt, ...);
且還需要先裝載mykmsg驅動,再來裝載要使用myprintk()的驅動,否則無法找到myprintk()函數
9.測試運行
如下圖所示,掛載了mykmsg驅動,可以看到生成了一個/proc/mykmsg文件
掛載/proc/mykmsg期間,其它驅動使用myprintk()函數,就會將信息列印在/proc/mykmsg文件中,如下圖所示:
和cat /proc/kmsg一樣,每次cat 都會清上一次的列印數據
10.若我們不想每次清,和dmesg命令一樣, 每次都能列印出環形緩衝區的所有信息,該如何改mykmsg驅動?
上次我們分析過了,每次調用read_buff()後,R都會+1。
要想不清空上次的信息列印,還需要定義一個R_ current標誌來代替R標誌,這樣每次cat結束後,R的位置保持不變。
每次cat時,系統除了進入file_operations-> read(),還會進入file_operations-> open(),所以在open()里,使R_ current=R,然後在修改部分代碼即可,
10.1我們還是以一個全局數組my_buff[7]為例, 如下圖所示:
10.2所以,修改的代碼如下所示:
#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 my_buff_len 1000 //環形緩衝區長度 static struct proc_dir_entry *my_entry; /* 聲明等待隊列類型中斷 mybuff_wait */ static DECLARE_WAIT_QUEUE_HEAD(mybuff_wait); static char my_buff[my_buff_len]; unsigned long R=0; //記錄讀的位置 unsigned long R_current=0; //記錄cat期間 讀的位置 unsigned long W=0; //記錄寫的位置 int read_buff(char *p) //p:指向要讀出的地址 { if(R_current==W) return 0; //讀失敗 *p=my_buff[R_current]; R_current=(R_current+1)%my_buff_len; //R_current++ return 1; //讀成功 } void write_buff(char c) //c:等於要寫入的內容 { my_buff [W]=c; W=(W+1)%my_buff_len; //W++ if(W==R) R=(R+1)%my_buff_len; //R++ if(W==R_current) R=(R+1)%my_buff_len; //R_current++ wake_up_interruptible(&mybuff_wait); //喚醒隊列,因為R !=W } /*列印到my_buff[]環形緩衝區中*/ int myprintk(const char *fmt, ...) { va_list args; int i,len; static char temporary_buff[my_buff_len]; //臨時緩衝區 va_start(args, fmt); len=vsnprintf(temporary_buff, INT_MAX, fmt, args); va_end(args); /*將臨時緩衝區放入環形緩衝區中*/ for(i=0;i<len;i++) { write_buff(temporary_buff[i]); } return len; } static int mykmsg_open(struct inode *inode, struct file *file) { R_current=R; return 0; } static int mykmsg_read(struct file *file, char __user *buf,size_t count, loff_t *ppos) { int error = 0,i=0; char c; if((file->f_flags&O_NONBLOCK)&&(R_current==W)) //非阻塞情況下,且沒有數據可讀 return -EAGAIN; error = -EINVAL; if (!buf || !count ) goto out; error = wait_event_interruptible(mybuff_wait,(W!=R_current)); if (error) goto out; while (!error && (read_buff(&c)) && i < count) { error = __put_user(c,buf); //上傳用戶數據 buf ++; i++; } if (!error) error = i; out: return error; } const struct file_operations mykmsg_ops = { .read = mykmsg_read, .open = mykmsg_open, }; static int mykmsg_init(void) { my_entry = create_proc_entry("mykmsg", S_IRUSR, &proc_root); if (my_entry) my_entry->proc_fops = &mykmsg_ops; return 0; } static void mykmsg_exit(void) { remove_proc_entry("mykmsg", &proc_root); } module_init(mykmsg_init); module_exit(mykmsg_exit); EXPORT_SYMBOL(myprintk); MODULE_LICENSE("GPL");
11.測試運行