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_printf和seq_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->count和seq->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->size為4096位元組 */
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->size為4096位元組情況下,顯示相關信息
* 由於函數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:您的支持是對博主最大的鼓勵