Linux内核PROC文件系统的初始化和主要函数分析

                                Linux内核PROC文件系统的初始化和主要函数分析
                                             written by aweii

一、概述(内核版本2.4.0)
系统启动初始化阶段,调用init_proc_fs进行proc文件系统的注册。内核初始化完成后,可由初始化用户进程(如init进程之类)调用mount -t proc /dev/null /proc 进行挂接。

二、结构
(1)虚拟目录结构
struct proc_dir_entry {
    unsigned short low_ino;
    unsigned short namelen;
    const char *name;
    mode_t mode;
    nlink_t nlink;
    uid_t uid;
    gid_t gid;
    unsigned long size;
    struct inode_operations * proc_iops;
    struct file_operations * proc_fops;
    get_info_t *get_info;
    struct module *owner;
    struct proc_dir_entry *next, *parent, *subdir;
    void *data;
    read_proc_t *read_proc;
    write_proc_t *write_proc;
    atomic_t count;        /* use count */
    int deleted;        /* delete flag */
    kdev_t    rdev;
};
inode->generic_ip指向proc_dir_entry,proc_lookup()通过proc_dir_entry的subdir链表搜寻其子节点的proc_dir_entry。

(2)

三、主要代码分析
(1)文件系统初始化
-------------init_proc_fs
static DECLARE_FSTYPE(proc_fs_type, "proc", proc_read_super, FS_SINGLE);
static int __init init_proc_fs(void)
{
    int err = register_filesystem(&proc_fs_type); //注册文件系统proc_fs_type,成员函数read_super指向proc_read_super
    if (!err) {
        proc_mnt = kern_mount(&proc_fs_type);//创建vfs_mount结构,挂接在proc_fs_type下,同时生成的元素包括超级块super_block和根节点root_inode
        err = PTR_ERR(proc_mnt);
        if (IS_ERR(proc_mnt))
            unregister_filesystem(&proc_fs_type);
        else
            err = 0;
    }
    return err;
}

(2)/proc/下的主要子节点初始化
--------------proc_misc_init
void __init proc_misc_init(void)
{
    struct proc_dir_entry *entry;
    static struct {
        char *name;
        int (*read_proc)(char*,char**,off_t,int,int*,void*);
    } *p, simple_ones[] = {
        {"loadavg",     loadavg_read_proc},
        {"uptime",    uptime_read_proc},
        {"meminfo",    meminfo_read_proc},
        {"version",    version_read_proc},
        {"cpuinfo",    cpuinfo_read_proc},
#ifdef CONFIG_PROC_HARDWARE
        {"hardware",    hardware_read_proc},
#endif
#ifdef CONFIG_STRAM_PROC
        {"stram",    stram_read_proc},
#endif
#ifdef CONFIG_DEBUG_MALLOC
        {"malloc",    malloc_read_proc},
#endif
#ifdef CONFIG_MODULES
        {"modules",    modules_read_proc},
        {"ksyms",    ksyms_read_proc},
#endif
        {"stat",    kstat_read_proc},
        {"devices",    devices_read_proc},
        {"partitions",    partitions_read_proc},
#if !defined(CONFIG_ARCH_S390)
        {"interrupts",    interrupts_read_proc},
#endif
        {"filesystems",    filesystems_read_proc},
        {"dma",        dma_read_proc},
        {"ioports",    ioports_read_proc},
        {"cmdline",    cmdline_read_proc},
#ifdef CONFIG_SGI_DS1286
        {"rtc",        ds1286_read_proc},
#endif
        {"locks",    locks_read_proc},
        {"mounts",    mounts_read_proc},
        {"swaps",    swaps_read_proc},
        {"iomem",    memory_read_proc},
        {"execdomains",    execdomains_read_proc},
        {NULL,}
    };
    for (p = simple_ones; p->name; p++)
        create_proc_read_entry(p->name, 0, NULL, p->read_proc, NULL);

    /* And now for trickier ones */
    entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
    if (entry)
        entry->proc_fops = &proc_kmsg_operations;
    proc_root_kcore = create_proc_entry("kcore", S_IRUSR, NULL);
    if (proc_root_kcore) {
        proc_root_kcore->proc_fops = &proc_kcore_operations;
        proc_root_kcore->size =
                (size_t)high_memory - PAGE_OFFSET + PAGE_SIZE;
    }
    if (prof_shift) {
        entry = create_proc_entry("profile", S_IWUSR | S_IRUGO, NULL);
        if (entry) {
            entry->proc_fops = &proc_profile_operations;
            entry->size = (1+prof_len) * sizeof(unsigned int);
        }
    }
#ifdef __powerpc__
    {
        extern struct file_operations ppc_htab_operations;
        entry = create_proc_entry("ppc_htab", S_IRUGO|S_IWUSR, NULL);
        if (entry)
            entry->proc_fops = &ppc_htab_operations;
    }
#endif
    entry = create_proc_read_entry("slabinfo", S_IWUSR | S_IRUGO, NULL,
                       slabinfo_read_proc, NULL);
    if (entry)
        entry->write_proc = slabinfo_write_proc;
}

(3)创建只读子节点的函数,能同时设置read_proc
-------------------------create_proc_read_entry
extern inline struct proc_dir_entry *create_proc_read_entry(const char *name,
    mode_t mode, struct proc_dir_entry *base,
    read_proc_t *read_proc, void * data)
{
    struct proc_dir_entry *res=create_proc_entry(name,mode,base); //创建名为name的proc_dir_entry,其父节点为base
    if (res) {
        res->read_proc=read_proc; //设置read_proc方法
        res->data=data;
    }
    return res;
}

(4)创建子节点的函数
-------------------------create_proc_entry
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                     struct proc_dir_entry *parent)
{
    struct proc_dir_entry *ent = NULL;
    const char *fn = name;
    int len;

    if (!parent && xlate_proc_name(name, &parent, &fn) != 0)
        goto out;
    len = strlen(fn);

    ent = kmalloc(sizeof(struct proc_dir_entry) + len + 1, GFP_KERNEL); //分配proc_dir_entry,其后紧跟name+"\0",所以空间大小为sizeof(struct proc_dir_entry) + strlen(name) + 1
    if (!ent)
        goto out;
    memset(ent, 0, sizeof(struct proc_dir_entry));
    memcpy(((char *) ent) + sizeof(*ent), fn, len + 1);//拷贝name到proc_dir_entry后
    ent->name = ((char *) ent) + sizeof(*ent); //name指向它
    ent->namelen = len;
        
    //目录节点设置方法
    if (S_ISDIR(mode)) {
        if ((mode & S_IALLUGO) == 0)
        mode |= S_IRUGO | S_IXUGO;
        ent->proc_fops = &proc_dir_operations;
        ent->proc_iops = &proc_dir_inode_operations;
        ent->nlink = 2;
    } else {
        if ((mode & S_IFMT) == 0)
            mode |= S_IFREG;
        if ((mode & S_IALLUGO) == 0)
            mode |= S_IRUGO;
        ent->nlink = 1;
    }
    ent->mode = mode;

    proc_register(parent, ent); //级联到父节点
    
out:
    return ent;
}

(5)目录搜索函数
根据级联关系进行循环搜索,和目录中搜索文件一样,都要比对名称
-------------------------proc_lookup
struct dentry *proc_lookup(struct inode * dir, struct dentry *dentry)
{
    struct inode *inode;
    struct proc_dir_entry * de;
    int error;

    error = -ENOENT;
    inode = NULL;
    de = (struct proc_dir_entry *) dir->u.generic_ip;
    if (de) {
        for (de = de->subdir; de ; de = de->next) {
            if (!de || !de->low_ino)
                continue;
            if (de->namelen != dentry->d_name.len)
                continue;
            if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
                int ino = de->low_ino;
                error = -EINVAL;
                inode = proc_get_inode(dir->i_sb, ino, de);
                break;
            }
        }
    }

    if (inode) {
        dentry->d_op = &proc_dentry_operations;
        d_add(dentry, inode);
        return NULL;
    }
    return ERR_PTR(error);
}

(6)文件通用读函数,它调用proc_dir_entry的get_info或read_proc成员函数
注意proc_file_read和具体的read_proc的搭配使用方法,ppos和其他普通文件读函数相同,
但要注意ppos、start值的交互变化。
proc_file_read循环调用read_proc,read_proc每次最多只读PROC_BLOCK_SIZE(4K页的3/4,3KB),
保证每次读入量都能容纳进page页面中。根据ppos内容在page中的偏移,调节start指针

-----------------------------proc_file_read
static struct file_operations proc_file_operations = {
    llseek:        proc_file_lseek,
    read:        proc_file_read,
    write:        proc_file_write,
};

#define PROC_BLOCK_SIZE    (PAGE_SIZE - 1024)

static ssize_t
proc_file_read(struct file * file, char * buf, size_t nbytes, loff_t *ppos)
{
    struct inode * inode = file->f_dentry->d_inode;
    char     *page;
    ssize_t    retval=0;
    int    eof=0;
    ssize_t    n, count;
    char    *start;
    struct proc_dir_entry * dp;

    dp = (struct proc_dir_entry *) inode->u.generic_ip;
    if (!(page = (char*) __get_free_page(GFP_KERNEL)))
        return -ENOMEM;

    while ((nbytes > 0) && !eof)
    {
        count = MIN(PROC_BLOCK_SIZE, nbytes);

        start = NULL;
        if (dp->get_info) {
            /*
             * Handle backwards compatibility with the old net
             * routines.
             */
            n = dp->get_info(page, &start, *ppos, count);
            if (n < count)
                eof = 1;
        } else if (dp->read_proc) {
            n = dp->read_proc(page, &start, *ppos,
                      count, &eof, dp->data);
        } else
            break;

        if (!start) {
            /*
             * For proc files that are less than 4k
             */
            start = page + *ppos;
            n -= *ppos;
            if (n <= 0)
                break;
            if (n > count)
                n = count;
        }
        if (n == 0)
            break;    /* End of file */
        if (n < 0) {
            if (retval == 0)
                retval = n;
            break;
        }
        
        /* This is a hack to allow mangling of file pos independent
          * of actual bytes read.  Simply place the data at page,
          * return the bytes, and set `start' to the desired offset
          * as an unsigned int. - Paul.Russell@rustcorp.com.au
         */
         n -= copy_to_user(buf, start < page ? page : start, n);
        if (n == 0) {
            if (retval == 0)
                retval = -EFAULT;
            break;
        }

        *ppos += start < page ? (long)start : n; /* Move down the file */
        nbytes -= n;
        buf += n;
        retval += n;
    }
    free_page((unsigned long) page);
    return retval;
}

(7)read_proc成员函数实例,读内核符号表
-------------------------read_proc举例
//见proc_misc_init()中的{"ksyms",    ksyms_read_proc},
static int ksyms_read_proc(char *page, char **start, off_t off,
                 int count, int *eof, void *data)
{
    int len = get_ksyms_list(page, start, off, count);
    if (len < count) *eof = 1;
    return len;
}

/*
begin始终指向buf处的内容在文件中的偏移,而
if (pos < offset) {
                len = 0;
                begin = pos;
            }
不断调节begin的位置使offset最终落到buf缓冲页(也就是上级函数传递过来的page)中
因此buf + (offset - begin)就得到offset对应的缓存页中的位置。
因为start可能>buf,为预防offset - begin + length 超出缓冲页,所以buf中只用了3/4空间限额,后边作预留。
由(6)知,每次调用read_proc时length最大值设置为PROC_BLOCK_SIZE(即3KB),此时,若当offset - begin>0,那么本段读入数据的末尾是超出3KB的,
但是不会超出4KB(begin的不断调节作用)。
*/
/*
 * Called by the /proc file system to return a current list of ksyms.
 */

int
get_ksyms_list(char *buf, char **start, off_t offset, int length)
{
    struct module *mod;
    char *p = buf;
    int len     = 0;    /* code from  net/ipv4/proc.c */
    off_t pos   = 0;
    off_t begin = 0;

    for (mod = module_list; mod; mod = mod->next) {
        unsigned i;
        struct module_symbol *sym;

        if (!MOD_CAN_QUERY(mod))
            continue;

        for (i = mod->nsyms, sym = mod->syms; i > 0; --i, ++sym) {
            p = buf + len;
            if (*mod->name) {
                len += sprintf(p, "%0*lx %s\t[%s]\n",
                           (int)(2*sizeof(void*)),
                           sym->value, sym->name,
                           mod->name);
            } else {
                len += sprintf(p, "%0*lx %s\n",
                           (int)(2*sizeof(void*)),
                           sym->value, sym->name);
            }
            pos = begin + len;
            if (pos < offset) {
                len = 0;
                begin = pos;
            }
            pos = begin + len;
            if (pos > offset+length)
                goto leave_the_loop;
        }
    }
leave_the_loop:
    *start = buf + (offset - begin);
    len -= (offset - begin);
    if (len > length)
        len = length;
    return len;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值