本文檔的Copyleft歸yfydz所有,使用GPL發布,可以自由拷貝,轉載,轉載時請保持文檔的完整性,嚴禁用於任何商業用途。
msn: yfydz_no1@hotmail.com
來源:http://yfydz.cublog.cn
1. 前言
在fs/seq_file.c中定義了關於seq操作的一系列順序讀取的函數,這些函數最早是在2001年就引入了,但以前內核中一直用得不多,而到了2.6內核后,許多/proc的只讀文件中大量使用了seq函數處理。
以下內核源碼版本為2.6.17.11。
2. seq相關數據結構
2.1 seq文件結構
struct seq_file {
char *buf;
size_t size;
size_t from;
size_t count;
loff_t index;
loff_t version;
struct mutex lock;
struct seq_operations *op;
void *private;
};
char *buf;
size_t size;
size_t from;
size_t count;
loff_t index;
loff_t version;
struct mutex lock;
struct seq_operations *op;
void *private;
};
struct seq_file描述了seq處理的緩沖區及處理方法,buf是動態分配的,大小不小於PAGE_SIZE,通常這個結構是通過struct file結構中的private_data來指向的。
char *buf:seq流的緩沖區
size_t size:緩沖區大小
size_t from:from指向當前要顯示的數據頭位置
size_t count:緩沖區中已有的數據長度
loff_t index:數據記錄索引值
loff_t version:版本號,是struct file的版本號的拷貝
struct mutex lock:seq鎖
struct seq_operations *op:seq操作結構,定義數據顯示的操作函數
void *private:私有數據
size_t size:緩沖區大小
size_t from:from指向當前要顯示的數據頭位置
size_t count:緩沖區中已有的數據長度
loff_t index:數據記錄索引值
loff_t version:版本號,是struct file的版本號的拷貝
struct mutex lock:seq鎖
struct seq_operations *op:seq操作結構,定義數據顯示的操作函數
void *private:私有數據
2.2 seq操作結構
seq的操作結構比較簡單,就是4個操作函數,完成開始、停止、顯示和取下一個操作:
/* include/linux/seq_file.h */
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);
};
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);
};
3. seq操作函數
seq操作包括以下一系列函數:
int seq_open(struct file *, struct seq_operations *);
打開seq流,為struct file分配struct seq_file結構,並定義seq_file的操作;
打開seq流,為struct file分配struct seq_file結構,並定義seq_file的操作;
ssize_t seq_read(struct file *, char __user *, size_t, loff_t *);
從seq流中讀數據到用戶空間,其中循環調用了struct seq_file中的各個函數來讀數據;
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = (struct seq_file *)file->private_data;
size_t copied = 0;
loff_t pos;
size_t n;
void *p;
int err = 0;
{
struct seq_file *m = (struct seq_file *)file->private_data;
size_t copied = 0;
loff_t pos;
size_t n;
void *p;
int err = 0;
// 先加鎖
mutex_lock(&m->lock);
/*
* 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 */
// 如果struct seq_file結構中的緩沖區沒有分配的話,
// 分配緩沖,大小為PAGE_SIZE
if (!m->buf) {
m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
if (!m->buf)
goto Enomem;
}
/* if not empty - flush it first */
// count表示當時有多少數據還沒有傳給用戶空間
// 盡量先將這些數據傳出
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 */
while (1) {
// 數據記錄的位置
pos = m->index;
// 初始化操作,返回值為對象相關指針
p = m->op->start(m, &pos);
err = PTR_ERR(p);
if (!p || IS_ERR(p))
break;
// 執行具體的顯示過程
err = m->op->show(m, p);
if (err)
break;
// 當前緩沖區中的實際數據小於緩沖區大小,轉到填數據部分
if (m->count < m->size)
goto Fill;
// 否則說明一個記錄的數據量太大,原來緩沖區大小不夠;
// 先停操作,重新分配緩沖區,大小增加一倍,重新操作,
// 要保證緩沖區大小大於一個數據記錄的大小
m->op->stop(m, p);
kfree(m->buf);
m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
if (!m->buf)
goto Enomem;
m->count = 0;
m->version = 0;
}
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;
}
err = m->op->show(m, p);
if (err || m->count == m->size) {
m->count = offs;
break;
}
pos = next;
}
// 停seq
m->op->stop(m, p);
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;
file->f_version = m->version;
mutex_unlock(&m->lock);
return copied;
Enomem:
err = -ENOMEM;
goto Done;
Efault:
err = -EFAULT;
goto Done;
}
mutex_lock(&m->lock);
/*
* 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 */
// 如果struct seq_file結構中的緩沖區沒有分配的話,
// 分配緩沖,大小為PAGE_SIZE
if (!m->buf) {
m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
if (!m->buf)
goto Enomem;
}
/* if not empty - flush it first */
// count表示當時有多少數據還沒有傳給用戶空間
// 盡量先將這些數據傳出
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 */
while (1) {
// 數據記錄的位置
pos = m->index;
// 初始化操作,返回值為對象相關指針
p = m->op->start(m, &pos);
err = PTR_ERR(p);
if (!p || IS_ERR(p))
break;
// 執行具體的顯示過程
err = m->op->show(m, p);
if (err)
break;
// 當前緩沖區中的實際數據小於緩沖區大小,轉到填數據部分
if (m->count < m->size)
goto Fill;
// 否則說明一個記錄的數據量太大,原來緩沖區大小不夠;
// 先停操作,重新分配緩沖區,大小增加一倍,重新操作,
// 要保證緩沖區大小大於一個數據記錄的大小
m->op->stop(m, p);
kfree(m->buf);
m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
if (!m->buf)
goto Enomem;
m->count = 0;
m->version = 0;
}
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;
}
err = m->op->show(m, p);
if (err || m->count == m->size) {
m->count = offs;
break;
}
pos = next;
}
// 停seq
m->op->stop(m, p);
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;
file->f_version = m->version;
mutex_unlock(&m->lock);
return copied;
Enomem:
err = -ENOMEM;
goto Done;
Efault:
err = -EFAULT;
goto Done;
}
loff_t seq_lseek(struct file *, loff_t, int);
定位seq流當前指針偏移;
int seq_release(struct inode *, struct file *);
釋放seq流所分配的動態內存空間,即struct seq_file的buf及其本身;
釋放seq流所分配的動態內存空間,即struct seq_file的buf及其本身;
int seq_escape(struct seq_file *, const char *, const char *);
將seq流中需要進行轉義的字符轉換為8進制數字;
int seq_putc(struct seq_file *m, char c);
向seq流中寫一個字符
int seq_puts(struct seq_file *m, const char *s);
向seq流中寫一個字符串
int seq_printf(struct seq_file *, const char *, ...)
__attribute__ ((format (printf,2,3)));
向seq流方式寫格式化信息;
int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);
在seq流中添加路徑信息,路徑字符都轉換為8進制數。
int seq_release_private(struct inode *, struct file *);
釋放seq_file的private然后進行seq_release
3. 用seq流填寫/proc文件
以下使用文件/proc/net/ip_conntrack的生成代碼來說明seq流的使用:
3.1 創立文件
以前2.4版本中使用proc_net_create()來建立/proc/net下的文件,現在使用seq流時要使用proc_net_fops_create()函數來創建,區別在於函數的最后一個參數,proc_net_create()的是一個函數指針,而proc_net_fops_create()的是一個文件操作指針:
......
proc = proc_net_fops_create("ip_conntrack", 0440, &ct_file_ops);
......
proc = proc_net_fops_create("ip_conntrack", 0440, &ct_file_ops);
......
proc_net_fops_create()函數其實也很簡單,調用create_proc_entry()函數建立/proc文件項,然后將文件項的操作結構指針指向所提供的文件操作指針:
static inline struct proc_dir_entry *proc_net_fops_create(const char *name,
mode_t mode, const struct file_operations *fops)
{
struct proc_dir_entry *res = create_proc_entry(name, mode, proc_net);
if (res)
res->proc_fops = fops;
return res;
}
mode_t mode, const struct file_operations *fops)
{
struct proc_dir_entry *res = create_proc_entry(name, mode, proc_net);
if (res)
res->proc_fops = fops;
return res;
}
3.2 文件操作結構
/proc/net/ip_conntrack所用的文件結構如下:
static struct file_operations ct_file_ops = {
.owner = THIS_MODULE,
.open = ct_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
可見,結構中除了open()函數是需要自定義外,其他的讀、定位、釋放函數都可以用seq標准函數。
.owner = THIS_MODULE,
.open = ct_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
可見,結構中除了open()函數是需要自定義外,其他的讀、定位、釋放函數都可以用seq標准函數。
3.3 open函數定義
open函數主要就是調用seq_open()函數將一個struct seq_operations結構和struct file鏈接起來,如果需要有私有數據的話,需要分配出動態空間作為struct seq_file的私有數據:
static int ct_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
struct ct_iter_state *st;
int ret;
{
struct seq_file *seq;
struct ct_iter_state *st;
int ret;
st = kmalloc(sizeof(struct ct_iter_state), GFP_KERNEL);
if (st == NULL)
return -ENOMEM;
ret = seq_open(file, &ct_seq_ops);
if (ret)
goto out_free;
seq = file->private_data;
seq->private = st;
memset(st, 0, sizeof(struct ct_iter_state));
return ret;
out_free:
kfree(st);
return ret;
}
if (st == NULL)
return -ENOMEM;
ret = seq_open(file, &ct_seq_ops);
if (ret)
goto out_free;
seq = file->private_data;
seq->private = st;
memset(st, 0, sizeof(struct ct_iter_state));
return ret;
out_free:
kfree(st);
return ret;
}
簡單的如exp_open()函數,就只調用seq_open()函數就完了:
static int exp_open(struct inode *inode, struct file *file)
{
return seq_open(file, &exp_seq_ops);
}
{
return seq_open(file, &exp_seq_ops);
}
3.4 seq操作結構
static struct seq_operations ct_seq_ops = {
.start = ct_seq_start,
.next = ct_seq_next,
.stop = ct_seq_stop,
.show = ct_seq_show
};
這個結構就是填寫4個操作函數:
start()函數完成讀數據前的一些預先操作,通常如加鎖,定位數據記錄位置等,該函數返回值就是show()函數第二個參數:
static void *ct_seq_start(struct seq_file *seq, loff_t *pos)
{
read_lock_bh(&ip_conntrack_lock);
return ct_get_idx(seq, *pos);
}
{
read_lock_bh(&ip_conntrack_lock);
return ct_get_idx(seq, *pos);
}
stop()函數完成讀數據后的一些恢復操作,如解鎖等:
static void ct_seq_stop(struct seq_file *s, void *v)
{
read_unlock_bh(&ip_conntrack_lock);
}
{
read_unlock_bh(&ip_conntrack_lock);
}
next()函數定位數據下一項:
static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
return ct_get_next(s, v);
}
{
(*pos)++;
return ct_get_next(s, v);
}
show()函數實現讀數據過程,將要輸出的數據直接用seq_printf()函數打印到seq流緩沖區中,由seq_printf()函數輸出到用戶空間:
static int ct_seq_show(struct seq_file *s, void *v)
{
// start()雖然返回的是struct list_head的指針,
// 但struct ip_conntrack_tuple_hash結構的第一
// 項參數就是struct list_head,所以可以進行直接
// 類型轉換而不用再計算偏移量
const struct ip_conntrack_tuple_hash *hash = v;
const struct ip_conntrack *conntrack = tuplehash_to_ctrack(hash);
struct ip_conntrack_protocol *proto;
{
// start()雖然返回的是struct list_head的指針,
// 但struct ip_conntrack_tuple_hash結構的第一
// 項參數就是struct list_head,所以可以進行直接
// 類型轉換而不用再計算偏移量
const struct ip_conntrack_tuple_hash *hash = v;
const struct ip_conntrack *conntrack = tuplehash_to_ctrack(hash);
struct ip_conntrack_protocol *proto;
ASSERT_READ_LOCK(&ip_conntrack_lock);
IP_NF_ASSERT(conntrack);
IP_NF_ASSERT(conntrack);
/* we only want to print DIR_ORIGINAL */
if (DIRECTION(hash))
return 0;
if (DIRECTION(hash))
return 0;
proto = __ip_conntrack_proto_find(conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum);
IP_NF_ASSERT(proto);
// 以下打印連接和協議信息
if (seq_printf(s, "%-8s %u %ld ",
proto->name,
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum,
timer_pending(&conntrack->timeout)
? (long)(conntrack->timeout.expires - jiffies)/HZ
: 0) != 0)
return -ENOSPC;
IP_NF_ASSERT(proto);
// 以下打印連接和協議信息
if (seq_printf(s, "%-8s %u %ld ",
proto->name,
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum,
timer_pending(&conntrack->timeout)
? (long)(conntrack->timeout.expires - jiffies)/HZ
: 0) != 0)
return -ENOSPC;
......
if (seq_printf(s, "use=%u\n", atomic_read(&conntrack->ct_general.use)))
return -ENOSPC;
return -ENOSPC;
return 0;
}
}
4. 結論
seq流函數的使用保證了數據能順序輸出,這也就是/proc只讀文件中使用它的最大原因吧。