35.Linux-分析並製作環形緩衝區

来源:http://www.cnblogs.com/lifexy/archive/2017/12/07/8000515.html
-Advertisement-
Play Games

在上章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_startlog_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.測試運行

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在本博客中,可以找到一篇《輸出的字元靠右對齊》http://www.cnblogs.com/insus/p/7953304.html 它有教大家怎樣實現字元串輸出進行左齊或者是右對齊。本篇的方法,超簡單,是使用string.Format()對本進行格式化輸出即可。 class Ad { privat ...
  • (1)創建WCF服務應用程式 (2)配置IIS 將WCF服務應用程式配置IIS網站,需要使用.net4.0集成版本的程式池 (3)使用SvcUtil.exe生成客戶端代碼和配置 SvcUtil.exe是一個VS命令行工具,該工具位於:C:\Program Files\Microsoft SDKs\W ...
  • 溫故而知新,今天學習Math.Max和Min的方法。這2個方法,均需要傳入2個參數,返回參數中最大值和最小值。 class Ac { public void LeanMathFunction() { int min = Math.Min(5,3); Console.WriteLine("5,3最小值 ...
  • XAML代碼: <ControlTemplate x:Key="btnTpl" TargetType="RadioButton"> <StackPanel Orientation="Vertical" Height="30" Background="Transparent"> <Border Nam ...
  • 1.創建服務引用 例如:天氣預報 2.在代碼添加引用空間 1 TvProgram.ChinaTVprogramWebService tp = new TvProgram.ChinaTVprogramWebService(); 2 DataSet ds= tp.getAreaDataSet(); 3. ...
  • 演示產品下載地址:http://www.jinhusns.com ...
  • 1.對稱加密 例子:壓縮文件加密碼,別人要打開,只能知道你的密碼,這樣的方法不安全,因為這個密碼可能是你的qq密碼或者是郵箱密碼等等 2.非對稱加密 類似於放羽毛球的桶,兩邊都可以拿資源,兩邊都加一個鎖,一個是我自己的公鑰,一個是我想給對方的公鑰 3. ssh scp是linux之間傳遞文件簡單的命 ...
  • 波特率就是發送二進位數據位的速率, 習慣上用 baud 表示, 即我們發送一位二進位數據的持續時間=1/baud。 在通信之前, 單片機 1 和單片機 2 首先都要明確的約定好它們之間的通信波特率, 必須保持一致, 收發雙方纔能正常實現通信, 這一點大家一定要記清楚。約定好速度後, 我們還要考慮第二 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...