seq_file文件的內核讀取過程


1 問題
seq_file只是在普通的文件read中加入了內核緩沖的功能,從而實現順序多次遍歷,讀取大數據量的簡單接口。seq_file一般只提供只讀接口,在使用seq_file操作時,主要靠下述四個操作來完成內核自定義緩沖區的遍歷的輸出操作,其中pos作為遍歷的iterator,在seq_read函數中被多次使用,用以定位當前從內核自定義鏈表中讀取的當前位置,當多次讀取時,pos非常重要,且pos總是遵循從0,1,2...end+1遍歷的次序,其即必須作為遍歷內核自定義鏈表的下標,也可以作為返回內容的標識。但是我在使用中僅僅將其作為返回內容的標示,並沒有將其作為遍歷鏈表的下標,從而導致返回數據量大時造成莫名奇妙的錯誤,注意:start返回的void*v如果非0,被show輸出后,在作為參數傳遞給next函數,next可以對其修改,也可以忽略;當next或者start返回NULL時,在seq_open中控制路徑到達seq_end。

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);
};

2 seq_file操作細節
2.0 struct seq_file結構體描述

struct seq_file {
    char *buf; //在seq_open中分配,大小為4KB
    size_t size; //4096
   size_t from; //struct file從seq_file中向用戶態緩沖區拷貝時相對於buf的偏移地址
    size_t count; //可以拷貝到用戶態的字符數目
    loff_t index; //從內核態向seq_file的內核態緩沖區buf中拷貝時start、next的處理的下標pos數值,即用戶自定義遍歷iter
    loff_t read_pos; //當前已拷貝到用戶態的數據量大小,即struct file中拷貝到用戶態的數據量
    u64 version; 
    struct mutex lock; //保護該seq_file的互斥鎖結構
    const struct seq_operations *op; //seq_start,seq_next,set_show,seq_stop函數結構體
    void *private;
};
*色為自定義內核相對於seq_file內核緩沖
*色為seq_file內核緩沖相對於用戶態緩沖區

2.1普通文件struct file的open函數建立seq_file於struct file即seq_file與struct seq_operation操作函數的連接關系

/**
 *    seq_open -    initialize sequential file
 *    @file: file we initialize
 *    @op: method table describing the sequence
 *
 *    seq_open() sets @file, associating it with a sequence described
 *    by @op.  @op->start() sets the iterator up and returns the first
 *    element of sequence. @op->stop() shuts it down.  @op->next()
 *    returns the next element of sequence.  @op->show() prints element
 *    into the buffer.  In case of error ->start() and ->next() return
 *    ERR_PTR(error).  In the end of sequence they return %NULL. ->show()
 *    returns 0 in case of success and negative number in case of error.
 *    Returning SEQ_SKIP means "discard this element and move on".
如果初始化出現問題:start,next返回ERR_PTR
如果結束時出現問題:遍歷結束時返回NULL
顯示正確時,show返回0,如果顯示錯誤則返回負值
*/
int seq_open(struct file *file, const struct seq_operations *op)
{
    struct seq_file *p = file->private_data;

    if (!p) {
        p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
            return -ENOMEM;
        file->private_data = p;
    }
    memset(p, 0, sizeof(*p));
    mutex_init(&p->lock);
    p->op = op;

    /*
     * Wrappers around seq_open(e.g. swaps_open) need to be
     * aware of this. If they set f_version themselves, they
     * should call seq_open first and then set f_version.
     */
    file->f_version = 0;

    /*
     * seq_files support lseek() and pread().  They do not implement
     * write() at all, but we clear FMODE_PWRITE here for historical
     * reasons.
     *
     * If a client of seq_files a) implements file.write() and b) wishes to
     * support pwrite() then that client will need to implement its own
     * file.open() which calls seq_open() and then sets FMODE_PWRITE.
     */
    file->f_mode &= ~FMODE_PWRITE;
    return 0;
}
EXPORT_SYMBOL(seq_open);

2.2 普通文件struct file的讀取函數為seq_read,完成seq_file的讀取過程
正常情況下分兩次完成:第一次執行執行seq_read時:start->show->next->show...->next->show->next->stop,此時返回內核自定義緩沖區所有內容,即copied !=0,所以會有第二次讀取操作
第二次執行seq_read時:由於此時內核自定義內容都返回,根據seq_file->index指示,所以執行start->stop,返回0,即copied=0,並退出seq_read操作
整體來看,用戶態調用一次讀操作,seq_file流程為:該函數調用struct seq_operations結構體順序為:start->show->next->show...->next->show->next->stop->start->stop來讀取順序文件

 
         
  1 /**
  2 * seq_read - ->read() method for sequential files.
  3 * @file: the file to read from
  4 * @buf: the buffer to read to
  5 * @size: the maximum number of bytes to read
  6 * @ppos: the current position in the file
  7 *
  8 * Ready-made ->f_op->read()
  9 */
 10 ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 11 {
 12     struct seq_file *m = (struct seq_file *)file->private_data;
 13     size_t copied = 0;
 14     loff_t pos;
 15     size_t n;
 16     void *p;
 17     int err = 0;
 18 
 19     mutex_lock(&m->lock);
 20 
 21     /* Don't assume *ppos is where we left it */
 22     if (unlikely(*ppos != m->read_pos))  //如果用戶已經讀取內容和seq_file中不一致,要將seq_file部分內容丟棄
 23     {
 24         m->read_pos = *ppos;
 25         while ((err = traverse(m, *ppos)) == -EAGAIN) //如果是這樣,首先通過seq_start,seq_show,seq_next,seq_show...seq_next,seq_show,seq_stop讀取*pos大小內容到seq_file的buf中
 26             ;
 27         if (err)
 28         {
 29             /* With prejudice... */
 30             m->read_pos = 0;
 31             m->version = 0;
 32             m->index = 0;
 33             m->count = 0;
 34             goto Done;
 35         }
 36     }
 37 
 38     /*
 39     * seq_file->op->..m_start/m_stop/m_next may do special actions
 40     * or optimisations based on the file->f_version, so we want to
 41     * pass the file->f_version to those methods.
 42     *
 43     * seq_file->version is just copy of f_version, and seq_file
 44     * methods can treat it simply as file version.
 45     * It is copied in first and copied out after all operations.
 46     * It is convenient to have it as part of structure to avoid the
 47     * need of passing another argument to all the seq_file methods.
 48     */
 49     m->version = file->f_version;
 50     /* grab buffer if we didn't have one */
 51     if (!m->buf)   如果第一次讀取seq_file,申請4K大小空間
 52     {
 53         m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
 54         if (!m->buf)
 55             goto Enomem;
 56     }
 57     /* if not empty - flush it first */
 58     if (m->count)  //如果seq_file中已經有內容,可能在前面通過traverse考了部分內容
 59     {
 60         n = min(m->count, size);
 61         err = copy_to_user(buf, m->buf + m->from, n); //拷貝到用戶態
 62         if (err)
 63             goto Efault;
 64         m->count -= n;
 65         m->from += n;
 66         size -= n;
 67         buf += n;
 68         copied += n;
 69         if (!m->count) //如果正好通過seq_序列操作拷貝了count個字節,從下個位置開始拷貝,不太清楚,traverse函數中,m->index已經增加過了,這里還要加?
 70             m->index++;
 71         if (!size)
 72             goto Done;
 73     }
 74     /* we need at least one record in buffer */
 75     pos = m->index;              //假設該函數從這里開始執行,pos=0,當第二次執行時,pos =上次遍歷的最后下標 + 1 >0,所以在start中,需要對pos非0特殊處理
 76     p = m->op->start(m, &pos);   //p為seq_start返回的字符串指針,pos=0;
 77     while (1)
 78     {
 79         err = PTR_ERR(p);
 80         if (!p || IS_ERR(p))       //如果通過start或next遍歷出錯,即返回的p出錯,則退出循環,一般情況下,在第二次seq_open時,通過start即出錯[pos變化],退出循環
 81             break;
 82         err = m->op->show(m, p);   //將p所指的內容顯示到seq_file結構的buf緩沖區中




83 if (err < 0) //如果通過show輸出出錯,退出循環,此時表明buf已經溢出 84 break; 85 if (unlikely(err)) //如果seq_show返回正常[即seq_file的buf未溢出,則返回0],此時將m->count設置為0,要將m->count設置為0 86 m->count = 0; 87 if (unlikely(!m->count)) //一般情況下,m->count==0,所以該判定返回false,#define unlikely(x) __builtin_expect(!!(x), 0)用於分支預測,提高系統流水效率 88 { 89 p = m->op->next(m, p, &pos); 90 m->index = pos; 91 continue; 92 }
93 if (m->count < m->size) //一般情況下,經過seq_start->seq_show到達這里[基本上是這一種情況],或者在err!=0 [即show出錯] && m->count != 0時到達這里 94 goto Fill; 95 m->op->stop(m, p); 96 kfree(m->buf); 97 m->buf = kmalloc(m->size <<= 1, GFP_KERNEL); 98 if (!m->buf) 99 goto Enomem; 100 m->count = 0; 101 m->version = 0; 102 pos = m->index; 103 p = m->op->start(m, &pos); 104 } 105 m->op->stop(m, p); 正常情況下,進入到這里,此時已經將所有的seq_file文件拷貝到buf中,且buf未溢出,這說明seq序列化操作返回的內容比較少,少於4KB 106 m->count = 0; 107 goto Done; 108 Fill: 109 /* they want more? let's try to get some more */ 110 while (m->count < size) 111 { 112 size_t offs = m->count; 113 loff_t next = pos; 114 p = m->op->next(m, p, &next); //一般情況在上面的while循環中只經歷了seq_start和seq_show函數,然后進入到這里,在這個循環里,執行下面循環: 115 if (!p || IS_ERR(p)) //如果seq_file的buf未滿: seq_next,seq_show,....seq_next->跳出 116 { //如果seq_file的buf滿了:則offs表示了未滿前最大的讀取量,此時p返回自定義結構內容的指針,但是后面show時候只能拷貝了該
117 err = PTR_ERR(p); //內容的一部分,導致m->cont == m->size判斷成立,從而m->count回滾到本次拷貝前,后面的pos++表示下次從下一個開始拷貝 118 break; 119 } 120 err = m->op->show(m, p); //我遇到的實際問題是show后,直接到stop,所以從這里退出了,應該是seq_file的buf填滿導致的問題,這里肯定是m->count == m->size 121 if (m->count == m->size || err) //如果seq_file的buf滿:   seq_next,seq_show,....seq_next,seq_show->跳出 122 { 123 m->count = offs; 124 if (likely(err <= 0)) 125 break; 126 } 127 pos = next; 128 }
129 m->op->stop(m, p); 最后執行seq_stop函數 130 n = min(m->count, size); 131 err = copy_to_user(buf, m->buf, n); //將最多size大小的內核緩沖區內容拷貝到用戶態緩沖區buf中 132 if (err) 133 goto Efault; 134 copied += n; 135 m->count -= n; 136 if (m->count) 如果本次給用戶態沒拷貝完,比如seq_file中count=100,但是n=10,即拷貝了前10個,則下次從10位置開始拷貝,這種情況一般不會出現 137 m->from = n; 138 else //一般情況下,pos++,下次遍歷時從next中的下一個開始,剛開始時,讓seq_func遍歷指針遞減,但是每次以k退出后,下次繼續從k遞減,原來是這里++了,所以遍歷最好讓指針遞增 139 pos++; 140 m->index = pos; 141 Done: 142 if (!copied) 143 copied = err; //copied = 0 144 else 145 { 146 *ppos += copied; 147 m->read_pos += copied; 148 } 149 file->f_version = m->version; 150 mutex_unlock(&m->lock); 151 return copied; //返回拷貝的字符數目,將copied個字符內容從seq_file的buf中拷貝到用戶的buf中 152 Enomem: 153 err = -ENOMEM; 154 goto Done; 155 Efault: 156 err = -EFAULT; 157 goto Done; 158 }
我的情況是:
執行流程為:... next->show-> stop->start-> stop->start->stop,這種問題出現是因為,在某次調用show過程中發現seq_file的buf滿了,此時m->count回退到調用前,然后調用stop函數,由於stop內容非常小,所以可以填入seq_file的buf,從而完成第一次fill_buf操作時,順帶有stop信息,操作完之后,此時pos=0,由於在seq_read末尾將其++,導致seq_file->index=1,然后第二次進入到seq_read中,此時可能出現err,導致該函數以非0返回,第3次時,才返回0.
 
         

2.3 將數據從自定義核心中拷貝到seq_file結構體的buf緩沖中的操作函數

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; //成功返回0,此時buf未滿
        }
    }
    m->count = m->size;
    return -1; //如果buf緩沖已滿,或者給buf輸出后,導致buf溢出,返回-1
}

附件:

1 錯誤代碼

 1 static void * seqStart (struct seq_file *m, loff_t *pos)
 2 {
 3     printk("--------int seqstart\n");
 4     spin_lock(&diskLog.lock);
 5     seq_printf(m,"the %d in seStart,pos =%lu\n",++countt,*pos);
 6     if (i >= LOG_RECORD_NUM || *pos != 0) 
 7         return NULL;
 8     else 
 9     {
10         *pos = (diskLog.currPos == 0) ? (LOG_RECORD_NUM - 1):(diskLog.currPos-1);
11         12       return diskLog.content[*pos];
13     }
14 }
15 static void seqStop (struct seq_file *m, void *v)
16 {
17     spin_unlock(&diskLog.lock);
18     printk("--------int seqstop\n");
19     seq_printf(m,"in seqStop\n");
20 }
21 static void * seqNext (struct seq_file *m, void *v, loff_t *pos)
22 {
23     i++;
24     printk("--------int seqnext\n");
25     seq_printf(m,"in seqNext,i=%d\n",i);
26     if(i >= LOG_RECORD_NUM)
27         return NULL;
28     if(*pos <= 0)
29          *pos = LOG_RECORD_NUM - 1;
30     else
31          *pos -=1;
32     poss = *pos; 
33     return diskLog.content[*pos];
34 }
35 static int seqShow (struct seq_file *m, void *v)
36 {
37     printk("--------int seqshow\n");
38     if(!i)
39         seq_printf(m,"\t\t--------The Recently Log Record--------\n");
40     if( *((char*)v) )
41     seq_printf(m,"i:%d,pos is:%d,currPos:%d,content:%s\n",i,poss,diskLog.currPos,(char*)v);
42     return 0;
43 }

可見,遍歷的條件變為了由自己定義的靜態變量i控制,而甩掉了pos,只用其做下標,而且並不是順序遞增操作,這樣可能和seq_read主讀取函數不一致,下面為跟蹤結果

2 linux kernel自帶的源代碼  seq_file.txt文件
 

 1 static void *ct_seq_start(struct seq_file *s, loff_t *pos)
 2  { loff_t *spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
if(!*pos)
return NULL;
3 if (! spos) return NULL; 4 *spos = *pos; //剛開始時,*pos=0 5 return spos; } 6 7 static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos) 8 { loff_t *spos = v; 9 *pos = ++*spos;
if(*pos > 100)
return NULL;
10 return spos; //返回1 } 11 12 static void ct_seq_stop(struct seq_file *s, void *v) 13 { kfree(v); } 14 15 static int ct_seq_show(struct seq_file *s, void *v) 16 { loff_t *spos = v; 17 seq_printf(s, "%lld\n", (long long)*spos); 18 return 0; } 19 20 static const struct seq_operations ct_seq_ops = { .start = ct_seq_start, 21 .next = ct_seq_next, 22 .stop = ct_seq_stop, 23 .show = ct_seq_show }; 24 25 static int ct_open(struct inode *inode, struct file *file) 26 { return seq_open(file, &ct_seq_ops); } 27 28 static const struct file_operations ct_file_ops = { .owner = THIS_MODULE, 29 .open = ct_open, 30 .read = seq_read, 31 .llseek = seq_lseek, 32 .release = seq_release }; 33 34 static int ct_init(void) 35 { struct proc_dir_entry *entry; 36 entry = create_proc_entry("sequence", 0, NULL); 37 if (entry) entry->proc_fops = &ct_file_ops; return 0; } 38 module_init(ct_init);

 

當用戶添加上上述紅色代碼行后,該結果輸出為0-100字符串

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM