话说jbd2_stats_proc_init把一个目录和两人个文件info和history建立起来,那么就可能通过这两个文件查看到内核的信息了,让我们再来好好看看这两个文件。
lan@lan-desktop:$ ls -l /proc/fs/jbd2/sda10\:8/
total 0
-r--r--r-- 1 root root 0 2009-09-11 22:15 history
-r--r--r-- 1 root root 0 2009-09-11 22:15 info
这两个目录都是只读的,为什么呢?
让我们来看看info这个目录的创建
proc_create_data("info", S_IRUGO, journal->j_proc_entry,&jbd2_seq_info_fops, journal);
这个函数以S_IRUGO的权限创建这个文件,S_IRUGO这个宏在include/linux/stat.h(52)定义,
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH), 意思是user, group, root都是只读。我还隐约记得有一种叫多米诺骨牌的东西,别看上面的那个函数简单,可是你看第四个参数,jbd2_seq_info_fops,这个牌可以把整个VFS系统弄倒,但为了不让我们的主题跑了,所以不作深入的讲解。先来看看这个变量
static struct file_operations jbd2_seq_info_fops = { .owner = THIS_MODULE, .open = jbd2_seq_info_open, .read = seq_read, .llseek = seq_lseek, .release = jbd2_seq_info_release, };
|
因为我们在执行
cat /proc/fs/jbd2/sda10\:8/info 的时候,实际上就是打开这个文件,然后再把它的内容读出来。所以我们主要来看这个结构体的两个成员open和read,这是两个函数指针,分别指向jbd2_seq_info_open和seq_read.它们分别在
static int jbd2_seq_info_open(struct inode *inode, struct file *file) { /* 获得这个节点的私有数据,因为在proc_create_data函数调用的时候,已经 把journal的指针赋给了inode所指的struc proc_dir_entry(include/linux/proc_fs.h)的data成员。 */ journal_t *journal = PDE(inode)->data; struct jbd2_stats_proc_session *s; int rc, size; /* 为jdb2一个统计会话分配空间,并把这个日志的统计信息拷贝到刚分配的空间里。 并做一些初始化工作。 */ s = kmalloc(sizeof(*s), GFP_KERNEL); if (s == NULL) return -ENOMEM; size = sizeof(struct transaction_stats_s); s->stats = kmalloc(size, GFP_KERNEL); if (s->stats == NULL) { kfree(s); return -ENOMEM; } spin_lock(&journal->j_history_lock); memcpy(s->stats, &journal->j_stats, size); s->journal = journal; spin_unlock(&journal->j_history_lock);
/* seq_open函数的作用是把file的private_data初始化为一个seq_file,并把这个 序列文件的操作对象赋成jbd2_seq_info_ops。这是一个序列文件的标准操作对像。 到这里我们就把文件打开好了。 */ rc = seq_open(file, &jbd2_seq_info_ops); if (rc == 0) { struct seq_file *m = file->private_data; m->private = s; } else { kfree(s->stats); kfree(s); } return rc;
}
|
在刚才的代码里,有一个jbd2_seq_info_ops对象,在journal.c(869)定义,这里面最重要的成员函数指针.show = jbd2_seq_info_show,
static struct seq_operations jbd2_seq_info_ops = { .start = jbd2_seq_info_start, .next = jbd2_seq_info_next, .stop = jbd2_seq_info_stop, .show = jbd2_seq_info_show, };
|
让我们来看看jbd2_seq_info_show(journal.c/833)这个函数的主要功能就是把日志的统计信息写到打开文件的private_data指向的seq_file里。
static int jbd2_seq_info_show(struct seq_file *seq, void *v) { struct jbd2_stats_proc_session *s = seq->private;
if (v != SEQ_START_TOKEN) return 0; seq_printf(seq, "%lu transaction, each upto %u blocks\n", s->stats->ts_tid, s->journal->j_max_transaction_buffers); if (s->stats->ts_tid == 0) return 0; seq_printf(seq, "average: \n %ums waiting for transaction\n", jiffies_to_msecs(s->stats->u.run.rs_wait / s->stats->ts_tid)); seq_printf(seq, " %ums running transaction\n", jiffies_to_msecs(s->stats->u.run.rs_running / s->stats->ts_tid)); seq_printf(seq, " %ums transaction was being locked\n", jiffies_to_msecs(s->stats->u.run.rs_locked / s->stats->ts_tid)); seq_printf(seq, " %ums flushing data (in ordered mode)\n", jiffies_to_msecs(s->stats->u.run.rs_flushing / s->stats->ts_tid)); seq_printf(seq, " %ums logging transaction\n", jiffies_to_msecs(s->stats->u.run.rs_logging / s->stats->ts_tid)); seq_printf(seq, " %lluus average transaction commit time\n", div_u64(s->journal->j_average_commit_time, 1000)); seq_printf(seq, " %lu handles per transaction\n", s->stats->u.run.rs_handle_count / s->stats->ts_tid); seq_printf(seq, " %lu blocks per transaction\n", s->stats->u.run.rs_blocks / s->stats->ts_tid); seq_printf(seq, " %lu logged blocks per transaction\n", s->stats->u.run.rs_blocks_logged / s->stats->ts_tid); return 0; }
|
这样,我们就的文件也有了信息的来源。
下面再让我们看看这些信息是怎么通过cat打开和读出数据来的,下面是我画了一简单的VFS操作图,只是为了示意一下。
由路径我们可以看出,当cat程序要打开文件是,最终调用的就是上面讲的jbd2_seq_info_open函数,而要读文件的时候,调用的也就是seq_read函数,在fs/seq_file.c(132)
/** * seq_read - ->read() method for sequential files. * @file: the file to read from * @buf: the buffer to read to * @size: the maximum number of bytes to read * @ppos: the current position in the file * * Ready-made ->f_op->read() */ /* 这个函数从一个序列文件读出数据到用户空间 * @file: 要读的文件指针,这里是/proc/fs/sda10\:8/info * @buf: 用户空间的指针 * @size: 最大要读取的字节数,有可能少于这个数,函数返回值是实际读取数。 * @ppos: 从文件的什么地方开始读。 */ 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; // 把文件锁起来,不让别的进程操作,这是一个互斥锁,关于linux的锁的机制,也是比较复杂的。
// 在这里就不多说了。
mutex_lock(&m->lock);
// 在每个文件内有一个值记录着文件当前读取位置,如果与传进来的值不相等,就设成传进近来的值。
// 并把文件的位置调整到*ppos, seq_file与一般的文件不一样,改变当然的位置要重新生成一次文件
// 的数据缓冲区,看看traverse的代码就可以知道了。在fs/seq_file.c(65),如果出错,就从0开始。
/* 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 */ if (!m->buf) { m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL); if (!m->buf) goto Enomem; } // 如果有数据还没刷到缓冲区,那就先flush到buf里,然后再拷贝到用户空间的缓冲区。
/* 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; } // 如果没有数据了,那就生成数据,下面的都是很简单的,其中m->op->show就是jbd2_seq_info_show函数,
// 数据就是从这里生成的。然后是一些清理工作。就不说了。
/* we need at least one record in buffer */ pos = m->index; p = m->op->start(m, &pos); while (1) { err = PTR_ERR(p); if (!p || IS_ERR(p)) break; err = m->op->show(m, p); if (err < 0) break; if (unlikely(err)) m->count = 0; if (unlikely(!m->count)) { p = m->op->next(m, p, &pos); m->index = pos; continue; } 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; 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; } 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); 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; }
|
到了这里,我们就把日志的建立,还有在 /proc/fs/jbd2目录的相关事宜讲完了。接下来就应该讲文件系统到底是怎样使用日志的。在这时只是先说一下,就是由于个函数分别是在fs/ext4/super.c(192)的ext4_journal_start_sb和(224)的__ext4_journal_stop。
阅读(1406) | 评论(0) | 转发(0) |