Chinaunix首页 | 论坛 | 博客
  • 博客访问: 97583
  • 博文数量: 14
  • 博客积分: 601
  • 博客等级: 中士
  • 技术积分: 247
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-05 14:26
文章分类

全部博文(14)

文章存档

2010年(3)

2009年(11)

我的朋友

分类: LINUX

2009-09-12 00:41:16

话说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。
阅读(5152) | 评论(1) | 转发(4) |
给主人留下些什么吧!~~

chinaunix网友2009-09-14 21:48:11

看的挺细致啊