Linux seq_printf輸出內容不完整的問題

来源:https://www.cnblogs.com/smith9527/archive/2018/12/24/10170710.html
-Advertisement-
Play Games

Linux seq_printf輸出內容不完整的問題 寫在前面的話:這是多年前在項目中遇到的問題,作為博客的開篇之作,有不足之處,請各位大俠斧正!謝謝! seq_file介面介紹 有許多種方法能夠實現設備驅動(或其它內核組件)提供信息給用戶或系統管理員。一個有用的技術是在debugfs,/proc或 ...


Linux seq_printf輸出內容不完整的問題

 寫在前面的話:這是多年前在項目中遇到的問題,作為博客的開篇之作,有不足之處,請各位大俠斧正!謝謝!

seq_file介面介紹

  有許多種方法能夠實現設備驅動(或其它內核組件)提供信息給用戶或系統管理員。一個有用的技術是在debugfs/proc或其他地方創建虛擬文件。虛擬文件能夠提供容易獲取的人類可讀的輸出,而且並不需要任何特殊的工具軟體,他們能夠減輕腳本作者的工作。

  seq_file介面就是其中一個能夠為內核模塊提供信息給用戶或管理員的介面,它通過在/proc目錄下創建虛擬文件,提供相關內核模塊的用戶介面。

struct seq_operations 結構體

  struct seq_operations提供了seq_file文件的迭代操作介面。其中:

  start用於起始訪問時文件的初始化工作,並返回一個鏈接(迭代)對象,或者SEQ_START_TOKEN(表示所有迴圈的開始);

  stop用於文件訪問結束時的清理工作,這裡文件訪問結束表示所有鏈接對象遍歷完畢;

  next用於在遍歷中尋找下一個鏈接對象;

  show用於對遍歷對象的操作,主要調用seq_printfseq_puts等函數,列印這個對象節點的信息。

struct seq_operations {

  void * (*start) (struct seq_file *m, loff_t *pos);

  void (*stop) (struct seq_file *m, void *v);

  void * (*next) (struct seq_file *m, void *v, loff_t *pos);

  int (*show) (struct seq_file *m, void *v);

}

內核模塊struct seq_operations結構體定義

在自研內核模塊中,skb_status_seq_ops實現當前skb使用狀態的列印,其定義如下:

static const struct seq_operations skb_status_seq_ops = {

  .start = xxxdriver_seq_start,

  .next  = xxxdriver_seq_next,

  .stop  = xxxdriver_seq_stop,

  .show  = skb_status_seq_show,

};

xxxdriver_seq_start:開始遍歷xxxdriver_dev_list的初始化工作;

xxxdriver_seq_next:遍歷xxxdriver_dev_list時返回一個xxxdriver鏈接對象;

xxxdriver_seq_stop:遍歷xxxdriver_dev_list完成時,做清理工作;

skb_status_seq_show:輸出遍歷對象的一些信息。

 

skb_status_seq_show函數中,通過seq_printf函數實現信息的列印,其實現如下:

static int skb_status_seq_show(struct seq_file *seq, void *v)

{

  if (v == SEQ_START_TOKEN)

  {

       seq_printf(seq, "%s", "skb status info start:\n");

         skbstatusflag = 1;

  }

  else

  {

    skb_status_seq_printf_stats(seq, v);

  }

  return 0;

}

  在調用xxxdriver_seq_start函數後,返回SEQ_START_TOKEN,首先輸出一行信息:“skb status info start:”。在遍歷xxxdriver_dev_list鏈接對象過程中,調用skb_status_seq_printf_stats函數,輸出實際的skb狀態信息。

  skb_status_seq_printf_stats函數列印的信息:xxxdriver在每個cpu上使用預分配skb的情況,包括預分配skb總數,空閑skb數目,已占用skb數目,skb為空的數目等等。

故障說明

  為了調試xxxdriver驅動,新增了skb_status_seq_show輸出內容,不幸的是,該功能不能顯示全部信息了,只顯示了“skb status info start:”信息。

  查看seq_printf函數的實現:

int seq_printf(struct seq_file *m, const char *f, ...)

{

  va_list args;

  int len;

  if (m->count < m->size) {

    va_start(args, f);

    len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);

    va_end(args);

    if (m->count + len < m->size) {

    m->count += len;

    return 0;

  }

}

m->count = m->size;

return -1;

}

  發現seq_printf函數能否繼續填充數據,需要判斷當前seq_file緩衝區是否還有空間可供使用。於是在xxxdriver skb_status_seq_printf_stats函數中列印seq->countseq->size的值,發現size大小為4096,而count值在填充完所有信息之前,已經到達4096位元組。想當然認為是由於seq_file緩衝區限制導致不能輸出全部信息。

seq_file文件輸出流程:

xxxdriver中,seq_flle文件指定的文件操作介面如下:

static const struct file_operations skb_status_seq_fops = {

  .owner  = THIS_MODULE,

  .open    = skb_status_seq_open,

  .read    = seq_read,

  .llseek  = seq_lseek,

  .release = seq_release,

};

seq_read函數定義如下:

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)

{

  struct seq_file *m = file->private_data;

  size_t copied = 0;

  loff_t pos;

  size_t n;

  void *p;

  int err = 0;

 

  mutex_lock(&m->lock);

  /* Don't assume *ppos is where we left it */

  if (unlikely(*ppos != m->read_pos)) {

    m->read_pos = *ppos;

    while ((err = traverse(m, *ppos)) == -EAGAIN);

    if (err) {

      /* With prejudice... */

      m->read_pos = 0;

      m->version = 0;

      m->index = 0;

      m->count = 0;

      goto Done;

    }

  }

 

  /*

   * seq_file->op->..m_start/m_stop/m_next may do special actions

   * or optimisations based on the file->f_version, so we want to

   * pass the file->f_version to those methods.

   *

   * seq_file->version is just copy of f_version, and seq_file

   * methods can treat it simply as file version.

   * It is copied in first and copied out after all operations.

   * It is convenient to have it as  part of structure to avoid the

   * need of passing another argument to all the seq_file methods.

   */

  m->version = file->f_version;

  /* grab buffer if we didn't have one */

     /* 初始化seq_file buf大小為一個page,即4096位元組 */

  if (!m->buf) {

    m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);

    if (!m->buf)

      goto Enomem;

  }

  /* if not empty - flush it first */

     /* 如果緩衝區有數據,先輸出 */

  if (m->count) {

    n = min(m->count, size);

    err = copy_to_user(buf, m->buf + m->from, n);

    if (err)

      goto Efault;

    m->count -= n;

    m->from += n;

    size -= n;

    buf += n;

    copied += n;

    if (!m->count)

      m->index++;

    if (!size)

      goto Done;

  }

  /* we need at least one record in buffer */

  pos = m->index;

   /* 對應於xxxdriver_seq_start函數,開始掃描驅動鏈接的對象 */

  p = m->op->start(m, &pos);

  while (1) {

    err = PTR_ERR(p);

    if (!p || IS_ERR(p))

      break;

    err = m->op->show(m, p); /*對應於xxxdriver_seq_show函數*/

     /* xxxdriver 會存放字元串:“skb status info start:” */

    if (err < 0)

      break;

    if (unlikely(err))

      m->count = 0;

    /* 如果沒有輸出,則查找下一個鏈接對象 */

    if (unlikely(!m->count)) {

      /*對應於xxxdriver_seq_next函數*/

      p = m->op->next(m, p, &pos); 

      m->index = pos;

      continue;

    }

    /* 如果輸出內容小於當前buf大小,則走填充數據流程 */

    /* xxxdriver skb_status_seq_show 輸出第一行數據後,會跳轉到Fill,去填充數據到用戶層,此時m->size4096位元組 */

    if (m->count < m->size)

      goto Fill;

    /* 如果我們一次性輸出內容大於當前seq_file->size,還有機會擴大緩衝區大小,重新輸出 */

    m->op->stop(m, p); /*對應於xxxdriver_seq_stop函數 */

    kfree(m->buf);

    /* 如果輸出內容大於當前buf大小,則擴展當前buf大小 */

    m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);

    if (!m->buf)

      goto Enomem;

    m->count = 0;

    m->version = 0;

    pos = m->index;

    p = m->op->start(m, &pos); /* 重新開始掃描鏈接對象 */

  }

  m->op->stop(m, p); /* 結束掃描鏈接對象 */

  m->count = 0;

  goto Done;

 

Fill:

  /* they want more? let's try to get some more */

     /* 如果當前緩衝區數據小於要讀取的數據,繼續獲取新數據 */

  while (m->count < size) {

    size_t offs = m->count;

    loff_t next = pos;

    p = m->op->next(m, p, &next); /* 獲取下一個鏈接對象 */

    /* 鏈接對象為空(鏈接末尾),或者有錯 */

    if (!p || IS_ERR(p)) { 

      err = PTR_ERR(p);

      break;

    }

    /* m->size4096位元組情況下,顯示相關信息

    * 由於函數skb_status_seq_printf_stats里輸出其餘數據大於4096位元組,* 則數據被截斷。offs的值為輸出第一行“skb status info start:”的長度 */

    err = m->op->show(m, p); /* 顯示相關數據 */

    if (m->count == m->size || err) { /* 已經填滿緩衝區或出錯 */

      m->count = offs;

      if (likely(err <= 0))

        break;

    }

    pos = next;

  }

  m->op->stop(m, p);

  /* 對於我們的問題,只copy第一行輸出 */

  n = min(m->count, size);

  err = copy_to_user(buf, m->buf, n); /* 拷貝數據到用戶層 */

  if (err)

    goto Efault;

  copied += n;

  m->count -= n;

  if (m->count)

    m->from = n;

  else

    pos++;

  m->index = pos;

Done:

  if (!copied)

    copied = err;

  else {

    *ppos += copied;

    m->read_pos += copied;

  }

  file->f_version = m->version;

  mutex_unlock(&m->lock);

  return copied;

Enomem:

  err = -ENOMEM;

  goto Done;

Efault:

  err = -EFAULT;

  goto Done;

}

 

PS:您的支持是對博主最大的鼓勵

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

-Advertisement-
Play Games
更多相關文章
  • 一位攝影程式員的獨白 每個人都有愛好,都有釋放壓力的活動,而我也不例外,我除了每天上班,周末就會約一群好友去拍妹子,成家後,就改為拍蟲子,一拍就到了30歲,到了30歲就感覺到了中年的壓力,這時候停下手中的攝影,開始研究技術,我開始翻閱各種技術博客,各種開源作品,靜下心去研究技術時,發現.NET的技術 ...
  • Win10的應用商店中,可以安裝喜馬拉雅,並下載感興趣的音頻文件。下載的音頻如下圖所示, 有兩個json文件(圖1),其中一個json文件(2677885list.json)包含了所下載音頻的詳細信息,包括id和title(文件真實名稱);音頻文件是一串純數字的id作為名稱,沒有顯示音頻的真實名稱( ...
  • C# -- 泛型的使用 1. 使用泛型 運行結果: 2. 泛型約束 運行結果: ...
  • Ssh登錄 Ssh是建立在應用層和傳輸層的安全協議,專門為遠程登錄回話和其他網路服務提供安全性。利用ssh可以有效的防止遠程管理中的信息泄露問題,同時ssh傳輸的數據是經過壓縮的,可以加快傳輸速度。 1、啟動sshd服務。Centos預設已經安裝了ssh,而且該服務預設是啟動的 [root@loca ...
  • 一 環境準備 1.1 Flannel概述 Flannel是一種基於overlay網路的跨主機容器網路解決方案,即將TCP數據包封裝在另一種網路包裡面進行路由轉發和通信,Flannel是CoreOS開發,專門用於docker多機互聯的一個工具,讓集群中的不同節點主機創建的容器都具有全集群唯一的虛擬ip ...
  • Mysql安裝和使用:點我有驚喜 ...
  • 環境 操作系統版本:Win7旗艦版64位系統 1、安裝FTP組件 ,打開過程可能會比較慢,大概3、4分鐘: 。勾選Internet信息服務下的 、`FTP服務 FTP擴展性 Web管理工具`全部項,如下圖: 2、添加FTP站點 在開始 程式中找到 ,並打開 右擊“網站”,選擇 : 在站點信息中,名稱 ...
  • 一 Docker Compose概述 Compose是一個用於定義和運行多容器Docker應用程式的工具。使用Compose,您可以使用YAML文件來配置應用程式的服務。然後,使用單個命令,您可以從配置中創建並啟動所有服務。 Compose適用於所有環境:生產,登臺,開發,測試以及CI工作流程。 使 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...