Linux的特殊文件系统-----

转载自:http://2005songliwei.blog.163.com/blog/static/169859420122148936313/


特殊文件系统 


特殊文件系统是只访问内核的内存数据而不访问块设备数据、用于特殊目的的文件系统。如:ramfs、debugfs、sysfs等,与一般文件系统的最大区别是它不用实现访问块设备的文件系统底层I/O接口。


本章介绍了常用的一些特殊文件系统ramfs、proc、debugfs和sysfs。ramfs是直接建立在内存缓存上的文件系统,在内核启动块设备还没挂接上时会用到它来存放某些文件;proc是内核用的文件系统,用于显示内核信息及改变内核参数;sysfs文件系统用来管理和显示各种设备的运行参数及设备的层次结构。




利用libfs创建特殊文件系统 


文件系统给用户提供了树型层次结构文件接口访问内核的数据,这些数据可来自块设备或其他内核产生的数据。访问块设备的数据由一般的文件系统完成,如:ext4、vfat文件系统,访问内核的数据由特殊的文件系统完成,如:proc、debugfs、sysfs文件系统。所有的文件系统都应虚拟文件系统定义的对象模型及接口。


虚拟文件系统定义的对象模型及接口包含大量的对象数据结构和方法函数,而特殊文件系统越来越多地用于各种应用目的,为了简化特殊文件系统的编写,内核提供了"libfs"的函数集,实现Linux文件系统API的普遍性操作,用户只须实现所需要提供的功能。


下面按照创建文件系统的步骤说明libfs提供的接口函数。




初始化超级块 
创建文件系统首先应创建超级块,创建超级块时还需要创建文件系统的根节点、根节点对应的dentry。超级块初始化后,说明文件系统已建立起来。函数simple_fill_super封装了初始化超级块的功能。其列出如下(在fs/libfs.c中): 


int simple_fill_super(struct super_block *s, int magic, struct tree_descr *files)  {   struct inode *inode;   struct dentry *root;   struct dentry *dentry;   int i;      s->s_blocksize = PAGE_CACHE_SIZE;   s->s_blocksize_bits = PAGE_CACHE_SHIFT;   s->s_magic = magic;   s->s_op = &simple_super_operations;  /*设置超级块操作函数集*/   s->s_time_gran = 1;      inode = new_inode(s);  /*创建节点,用作根节点*/   if (!inode)    return -ENOMEM;   /*由于根节点号为1,因此,数组files不能包括数组序号为1的条目*/   inode->i_ino = 1;   inode->i_mode = S_IFDIR | 0755;   inode->i_uid = inode->i_gid = 0;   inode->i_blocks = 0;   inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;   inode->i_op = &simple_dir_inode_operations;   inode->i_fop = &simple_dir_operations;   inode->i_nlink = 2;   root = d_alloc_root(inode);  /*创建节点对应的dentry*/   if (!root) {    iput(inode);    return -ENOMEM;   }          /*创建函数参数传入的文件数组files中各文件名对应的节点和dentry*/   for (i = 0; !files->name || files->name[0]; i++, files++) {    if (!files->name)     continue;       /* 如果节点号与根节点发生冲突,发出警告*/    if (unlikely(i == 1))     printk(KERN_WARNING "%s: %s passed in a files array"      "with an index of 1!\n", __func__,      s->s_type->name);       dentry = d_alloc_name(root, files->name);    if (!dentry)     goto out;    inode = new_inode(s);    if (!inode)     goto out;    inode->i_mode = S_IFREG | files->mode;    inode->i_uid = inode->i_gid = 0;    inode->i_blocks = 0;    inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;    inode->i_fop = files->ops;    inode->i_ino = i;    d_add(dentry, inode);   }   s->s_root = root;   return 0;  out:   d_genocide(root);   dput(root);   return -ENOMEM;  }


操作函数集 


初始化文件系统的超级块后,表示文件系统已建立起来,此时,文件系统已创建了超级块对象、根目录和根节点对象。文件系统的对象还需要创建方法函数或者实现操作函数集结构中的函数。


在编写特殊文件系统时,通常超级块、目录、节点对象的操作使用通用的操作函数集,文件操作部分使用通用的操作函数集,部分实现用户特定的功能,特别是读与写操作。下面分别说明操作函数集。




(1)目录的文件操作函数集 
目录的文件操作函数集simple_dir_operations列出如下: 


const struct file_operations simple_dir_operations = {   .open  = dcache_dir_open,   .release = dcache_dir_close,   .llseek  = dcache_dir_lseek,   .read  = generic_read_dir,   .readdir = dcache_readdir,   .fsync  = simple_sync_file,  };


(2)目录节点操作函数集 目录节点的节点操作函数集simple_dir_inode_operations列出如下: 


const struct inode_operations simple_dir_inode_operations = {   .lookup  = simple_lookup,  };


(3)超级块操作函数集 超级块操作函数集simple_super_operations列出如下: 


static const struct super_operations simple_super_operations = {   .statfs  = simple_statfs,  };


(4)地址空间操作函数集 地址空间操作函数集ramfs_aops列出如下: 


const struct address_space_operations ramfs_aops = {   .readpage = simple_readpage,   .write_begin = simple_write_begin,   .write_end = simple_write_end,   .set_page_dirty = __set_page_dirty_no_writeback,  };


(5)文件操作函数集 文件操作函数集ramfs_file_operations列出如下: 


const struct file_operations ramfs_file_operations = {   .read  = do_sync_read,   .aio_read = generic_file_aio_read,   .write  = do_sync_write,   .aio_write = generic_file_aio_write,   .mmap  = generic_file_mmap,   .fsync  = simple_sync_file,   .splice_read = generic_file_splice_read,   .splice_write = generic_file_splice_write,   .llseek  = generic_file_llseek,  };


(6)文件节点操作函数集 文件节点操作函数集ramfs_file_inode_operations列出如下: 


const struct inode_operations ramfs_file_inode_operations = {   .getattr = simple_getattr,  };        ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)  {   struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };   struct kiocb kiocb;   ssize_t ret;      init_sync_kiocb(&kiocb, filp);   kiocb.ki_pos = *ppos;   kiocb.ki_left = len;      for (;;) {    ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);    if (ret != -EIOCBRETRY)     break;    wait_on_retry_sync_kiocb(&kiocb);   }      if (-EIOCBQUEUED == ret)    ret = wait_on_sync_kiocb(&kiocb);   *ppos = kiocb.ki_pos;   return ret;  }     ssize_t generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,    unsigned long nr_segs, loff_t pos)  {   struct file *file = iocb->ki_filp;   struct address_space *mapping = file->f_mapping;   struct inode *inode = mapping->host;   ssize_t ret;      BUG_ON(iocb->ki_pos != pos);      mutex_lock(&inode->i_mutex);   ret = __generic_file_aio_write_nolock(iocb, iov, nr_segs,     &iocb->ki_pos);   mutex_unlock(&inode->i_mutex);      if (ret > 0 && ((file->f_flags & O_SYNC) || IS_SYNC(inode))) {    ssize_t err;       err = sync_page_range(inode, mapping, pos, ret);    if (err < 0)     ret = err;   }   return ret;  }


挂接文件系统 


文件系统挂接后才能被用户使用。一般文件系统通常由用户空间的进程或工具程序(通常为shell命令mount)进行挂接。特殊文件系统通常由由特殊用途的内核线程挂接。libfs提供了接口函数simple_pin_fs,检查文件系统是否挂接,如果没有挂接,就挂接文件系统。其参数mount返回已挂接文件系统的结构vfsmount实例,参数count返回挂接操作次数。


函数simple_pin_fs列出如下: 


int simple_pin_fs(struct file_system_type *type, struct vfsmount **mount, int *count)  {   struct vfsmount *mnt = NULL;   spin_lock(&pin_fs_lock);      /*如果*mount为空,说明文件系统未挂接,unlikely表示这种情况较少发生,用于编译优化*/   if (unlikely(!*mount)) {      spin_unlock(&pin_fs_lock);           /*挂接文件系统,挂接成功后,会创建并初始化结构vfsmount实例*/    mnt = vfs_kern_mount(type, 0, type->name, NULL);    if (IS_ERR(mnt))     return PTR_ERR(mnt);    spin_lock(&pin_fs_lock);    if (!*mount)   /*挂接成功,将结构vfsmount实例放在函数参数mount中返回*/     *mount = mnt;   }   mntget(*mount);   ++*count;        /*文件系统挂接操作次数加1,计数值在函数参数count中返回*/   spin_unlock(&pin_fs_lock);   mntput(mnt);   return 0;  }


文件系统debugfs 


内核开发者可使用文件系统debugfs向用户空间应用输出调试信息,这些调试信息只在需要时输出,在系统正常运行中则不需要这些调试信息。用户可在该虚拟文件系统中创建一个或多个文件向用户空间提供调试信息。内核的kprobe调试、relay调试数据输出等都依赖于该文件系统。




内核debugfs API说明 


文件系统debugfs的文件和目录由内核线程创建,由用户空间的应用程序访问。内核线程通过debugfs API创建文件和目录以及删除文件或目录,这些API说明如下:


(1)创建目录


用户首先需要创建一个目录,创建目录的接口函数声明列出如下:




 


struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);


 


其中,参数name指向将创建的目录名,参数parent指定创建目录的父目录,如果为NULL,表示在debugfs根目录下创建目录。如果返回为-ENODEV,表示内核没有把debugfs编译到其中,如果返回为NULL,表示其他类型的创建失败,如果创建目录成功,返回指向该目录对应的dentry条目的指针。


(2)创建文件


用户创建一个文件,用于输出/输出调试数据或调试控制参数等。创建文件的函数接口声明列出如下(在include/linux/debugfs.h中): 


struct dentry *debugfs_create_file(const char *name, mode_t mode,         struct dentry *parent, void *data,         const struct file_operations *fops);


其中,参数name指向将创建的文件名,参数mode指定该文件的访问模式,参数parent指向该文件所在目录,参数data为文件特定的一些数据,参数fops该文件的文件操作函数集,通常由用户实现,或者使用文件系统proc提供了文件操作函数集。 debugfs还提供了简单的API用于调试可控制的变量,部分列出如下: 


struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry,                  struct dentry *new_dir, const char *new_name);     struct dentry *debugfs_create_u8(const char *name, mode_t mode,       struct dentry *parent, u8 *value);  ……  struct dentry *debugfs_create_blob(const char *name, mode_t mode,        struct dentry *parent,        struct debugfs_blob_wrapper *blob);


(3)删除文件或目录


函数debugfs_remove从文件系统debugfs删除一个文件或目录。参数dentry指向删除文件或目录的dentry,其函数原型列出如下:




 


void debugfs_remove(struct dentry *dentry)


 


(4)文件或目录重命名


函数debugfs_rename重命名或移动一个文件或目录。其中,参数old_dir为命名对象的父dentry,应该是一个目录的dentry;参数old_dentry为对象重命名前的dentry;参数new_dir为将被移动目标父dentry;参数new_name为对象重命名后的dentry。


函数debugfs_rename的原型列出如下: 


struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry,    struct dentry *new_dir, const char *new_name)


注册文件系统类型 
创建文件系统的第一步是创建并初始化文件系统超级块。文件系统debugfs通过调用函数debugfs_init初始化文件系统,该函数在文件系统sysfs中创建debug目录,注册文件类型,调用libfs接口函数simple_fill_super初始化超级块,其列出如下(在fs/debugfs/inode.c中): 


static int __init debugfs_init(void)  {   int retval;         /*在文件系统sysfs中创建/sys/kernel/debug目录*/   debug_kobj = kobject_create_and_add("debug", kernel_kobj);   if (!debug_kobj)    return -EINVAL;      /*注册文件系统类型*/   retval = register_filesystem(&debug_fs_type);   if (retval)    kobject_put(debug_kobj);   return retval;  }     static struct file_system_type debug_fs_type = {   .owner = THIS_MODULE,   .name =  "debugfs",   .get_sb = debug_get_sb,          /*创建并初始化超级块*/   .kill_sb = kill_litter_super,  /*删除超级块*/  };


函数debug_get_sb创建并初始化超级块,其列出如下: 


static int debug_get_sb(struct file_system_type *fs_type,  
   int flags, const char *dev_name,     void *data, struct vfsmount *mnt)  {   return get_sb_single(fs_type, flags, data, debug_fill_super, mnt);  


  }


函数debug_fill_super调用libfs接口函数simple_fill_super初始化超级块,其列出如下: 


static int debug_fill_super(struct super_block *sb, void *data, int silent)  
{   static struct tree_descr debug_files[] = {{""}};      return simple_fill_super(sb, DEBUGFS_MAGIC, debug_files);  


  }


函数debugfs_create_file分析 


函数debugfs_create_file在文件系统debugfs的指定目录parent中创建一个名为name的文件。函数debugfs_create_file调用层次图如图3所示。








Linux special filesystem 02.gif
图3 函数debugfs_create_file调用层次图函数debugfs_create_file列出如下(在fs/debugfs.c中): 


struct dentry *debugfs_create_file(const char *name, mode_t mode,         struct dentry *parent, void *data,         const struct file_operations *fops)  {   struct dentry *dentry = NULL;   int error;      pr_debug("debugfs: creating file '%s'\n",name);          /*检查文件系统是否挂接,如果没有挂接,就挂接文件系统,参数debugfs_mount返回文件系统挂接描述结构对象,参数debugfs_mount_count返回挂接操作计数*/   error = simple_pin_fs(&debug_fs_type, &debugfs_mount,           &debugfs_mount_count);   if (error)    goto exit;      /*创建文件名对应的dentry和节点对象*/   error = debugfs_create_by_name(name, mode, parent, &dentry);   if (error) { /*如果创建错误,卸载文件系统*/    dentry = NULL;    simple_release_fs(&debugfs_mount, &debugfs_mount_count);    goto exit;   }      if (dentry->d_inode) {     if (data)     dentry->d_inode->i_private = data;  /*赋上私有数据*/    if (fops)     dentry->d_inode->i_fop = fops;    /*赋上节点的文件操作函数集*/   }  exit:   return dentry;  }


函数debugfs_create_by_name查找文件名name,创建dentry和节点对象,赋上操作函数集,其列出如下: 


static int debugfs_create_by_name(const char *name, mode_t mode,  
      struct dentry *parent,        struct dentry **dentry)  {   int error = 0;      /* 如果没指定父目录,就指定父目录为文件系统的根目录,即文件系统挂接点的目录*/   if (!parent) {    if (debugfs_mount && debugfs_mount->mnt_sb) {     parent = debugfs_mount->mnt_sb->s_root;  /*为文件系统挂接点的目录*/    }   }   if (!parent) {    pr_debug("debugfs: Ah! can not find a parent!\n");    return -EFAULT;   }      *dentry = NULL;   mutex_lock(&parent->d_inode->i_mutex);   *dentry = lookup_one_len(name, parent, strlen(name)); /*查找文件名name,创建dentry*/   if (!IS_ERR(*dentry)) {    switch (mode & S_IFMT) {    case S_IFDIR:  /*创建目录*/     error = debugfs_mkdir(parent->d_inode, *dentry, mode);     break;    case S_IFLNK: /*创建文件链接*/     error = debugfs_link(parent->d_inode, *dentry, mode);     break;    default:  /*创建文件*/     error = debugfs_create(parent->d_inode, *dentry, mode);     break;    }    dput(*dentry);   } else    error = PTR_ERR(*dentry);   mutex_unlock(&parent->d_inode->i_mutex);      return error;  


  }


创建文件或目录都需要调用函数debugfs_mknod创建节点对象,创建文件的函数为debugfs_create,它创建节点对象后,通知inotify机制"已创建节点"消息。其列出如下: 


static int debugfs_create(struct inode *dir, struct dentry *dentry, int mode)  
{   int res;      mode = (mode & S_IALLUGO) | S_IFREG;   res = debugfs_mknod(dir, dentry, mode, 0);  


  
   /*如果节点创建成功,通知inotify机制“节点已创建”,inotify机制用于监控文件系统的变化*/  
  
 if (!res)      fsnotify_create(dir, dentry);   return res;  


  }


函数debugfs_mknod创建节点对象,在dentry中填充节点信息,这样,将dentry与节点关联起来。由路径名可访问dentry,由dentry可访问节点,反之一样。 函数debugfs_mknod列出如下: 


/* SMP-safe */  static int debugfs_mknod(struct inode *dir, struct dentry *dentry,      int mode, dev_t dev)  {   struct inode *inode;   int error = -EPERM;      if (dentry->d_inode)    return -EEXIST;       /*创建节点对象*/   inode = debugfs_get_inode(dir->i_sb, mode, dev);   if (inode) {    d_instantiate(dentry, inode); /*填充dentry中的节点信息,将dentry与节点关联起来*/    dget(dentry);  /*dentry引用计数加1*/    error = 0;   }   return error;  }


函数debugfs_get_inode创建节点对象,将节点对象加入到节点链表中,给节点赋上文件操作函数集和节点操作函数集,其列出如下: 


static struct inode *debugfs_get_inode(struct super_block *sb, int mode, dev_t dev)  
{  


  
    /*创建节点对象,将节点对象加入到节点链表中,节点号为上一个节点加1*/  
  
 struct inode *inode = new_inode(sb);       if (inode) {  /*初始化节点*/    inode->i_mode = mode;    inode->i_uid = 0;    inode->i_gid = 0;    inode->i_blocks = 0;    inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;    switch (mode & S_IFMT) {    default:     init_special_inode(inode, mode, dev);  /*初始化特殊节点*/     break;    case S_IFREG: /*赋上常规文件操作函数集*/     inode->i_fop = &debugfs_file_operations;     break;    case S_IFLNK: /*赋上文件链接的操作函数集*/     inode->i_op = &debugfs_link_operations;     break;    case S_IFDIR:  /*赋上目录的节点和文件操作函数集*/     inode->i_op = &simple_dir_inode_operations;     inode->i_fop = &simple_dir_operations;        /* 目录节点因为存在“.”条目,i_nlink从2开始,因此,i_nlink加1*/     inc_nlink(inode);     break;    }   }   return inode;   


  }


文件操作函数集结构实例debugfs_file_operations列出如下: 


const struct file_operations debugfs_file_operations = {  
 .read =  default_read_file,   .write = default_write_file,   .open =  default_open,  


  };


文件链接的节点操作函数集结构实例debugfs_link_operations列出如下: 


const struct inode_operations debugfs_link_operations = {  
 .readlink       = generic_readlink,   .follow_link    = debugfs_follow_link,  


  };


ramfs内存文件系统 


ramfs是一个利用VFS自身结构而形成的内存文件系统。ramfs没有自已的文件存储结构,它的文件存储于页高速缓存(page cache)中,目录结构由dentry链表本身描述,文件则由VFS的inode结构本身描述。ramfs不能格式工,缺省情况下,ramfs被限制可使用内存大小的一半。存放在ramfs中的数据在系统重启动后会丢失。


使用ramfs的方法如下: 


# mkdir -p /mountdir  # mount -t ramfs none /mountdir


 








ramfs文件系统模块初始化 


在ramfs/inode.c中有ramfs文件系统模块初始化及退出函数,函数列出如下:


static DECLARE_FSTYPE(ramfs_fs_type, "ramfs", ramfs_read_super, FS_LITTER); 


static DECLARE_FSTYPE(ramfs_fs_type, "ramfs", ramfs_read_super, FS_LITTER);  static int __init init_ramfs_fs(void)  {   return register_filesystem(&ramfs_fs_type);  }  static void __exit exit_ramfs_fs(void)  {   unregister_filesystem(&ramfs_fs_type);  }


 




  ramfs文件系统模块初始化把超级块加到文件系统链表中,由函数ramfs_read_super得到超级块,下面分析ramfs_read_super函数(在fs/ramfs/inode.c中):








Linux special filesystem 01.gif
图 9 1:ramfs_read_super函数调用层次图ramfs_read_super函数初始化super_block参数,注册&ramfs_ops,调用ramfs_get_inode()设置inode结构及其中操作函数。ramfs_read_super函数调用层次图如上图,ramfs_read_super函数分析如下: 


static struct super_block *ramfs_read_super(struct super_block * sb, void * data, int silent)  {   struct inode * inode;   struct dentry * root;      sb->s_blocksize = PAGE_CACHE_SIZE;   sb->s_blocksize_bits = PAGE_CACHE_SHIFT;   sb->s_magic = RAMFS_MAGIC;   sb->s_op = &ramfs_ops;      //在缓存中分配inode结构,初始化,赋上最后节点号,最后节点号+1   inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0);         //创建dentry,将inode加入成员,返回dentry   root = d_alloc_root(inode);   ……   }   sb->s_root = root;   return sb;  }


  ramfs_get_inode函数新建inode,并赋上初值及操作函数集。函数分析如下: 


struct inode *ramfs_get_inode(struct super_block *sb, int mode, int dev)  {   struct inode * inode = new_inode(sb);      if (inode) {    inode->i_mode = mode;    inode->i_uid = current->fsuid;    inode->i_gid = current->fsgid;    inode->i_blksize = PAGE_CACHE_SIZE;    inode->i_blocks = 0;    inode->i_rdev = to_kdev_t(dev);    inode->i_mapping->a_ops = &ramfs_aops;    inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;    switch (mode & S_IFMT) {    default:              //与设备文件相联的inode函数     init_special_inode(inode, mode, dev);      break;    case S_IFREG: 普通文件的inode函数     inode->i_fop = &ramfs_file_operations;     break;    case S_IFDIR: 目录文件的inode函数     inode->i_op = &ramfs_dir_inode_operations;     inode->i_fop = &ramfs_dir_operations;     break;    case S_IFLNK: 符号链接的inode函数     inode->i_op = &page_symlink_inode_operations;     break;    }   }   return inode;  }


 




init_special_inode函数在fs/devices.c中,用来连接特殊节点(如设备节点)的操作函数集,函数分析如下:




 


void init_special_inode(struct inode *inode, umode_t mode, int rdev)  {   inode->i_mode = mode;   if (S_ISCHR(mode)) { 如果为字符设备文件,提供字符设备的inode函数    inode->i_fop = &def_chr_fops;    inode->i_rdev = to_kdev_t(rdev);   } else if (S_ISBLK(mode)) { 如果为块设备文件,提供块设备的inode函数    inode->i_fop = &def_blk_fops;    inode->i_rdev = to_kdev_t(rdev);    inode->i_bdev = bdget(rdev); 指向块设备描述结构   } else if (S_ISFIFO(mode)) 如果为FIFO文件,提供FIFO的inode函数    inode->i_fop = &def_fifo_fops;   else if (S_ISSOCK(mode)) 如果为SOCK文件,提供SOCK的inode函数    inode->i_fop = &bad_sock_fops;   else    printk(KERN_DEBUG "init_special_inode: bogus imode (%o)\n", mode);  }


ramfs文件系统操作函数集 
通过模块初始化调用ramfs_read_super函数后,ramfs文件系统各种操作函数集实例就被赋到节点结构的成员指针上,以后就通过inode或dentry就可以访问这些操作函数集,下面列出了ramfs文件系统操作函数集(在fs/ramfs/inode.c中): 


static struct super_operations ramfs_ops = {   statfs:  ramfs_statfs,   put_inode: force_delete,  };  static struct file_operations ramfs_file_operations = {   read:  generic_file_read, //数据文件的通用高级读函数   write:  generic_file_write, //数据文件的通用高级写函数   mmap:  generic_file_mmap,//数据文件的通用高级内存映射函数   fsync:  ramfs_sync_file,  };  static struct file_operations ramfs_dir_operations = {   read:  generic_read_dir, //返回-EISDIR错误的函数   readdir: dcache_readdir, //目录文件的高级读目录函数   fsync:  ramfs_sync_file,  };  static struct inode_operations ramfs_dir_inode_operations = {   create:  ramfs_create,   lookup:  ramfs_lookup,   link:  ramfs_link,   unlink:  ramfs_unlink,   symlink: ramfs_symlink,   mkdir:  ramfs_mkdir,   rmdir:  ramfs_rmdir,   mknod:  ramfs_mknod,   rename: ramfs_rename,  };


 








文件读写操作 


当sys_read或sys_write系统调用调用文件节点中的操作函数集ramfs_file_operations时,它调用了通用的读写函数,这些通用的读写函数再调用文件的低级页操作接口ramfs_aops函数操作集来完成读写操作。ramfs_aops函数操作结构如下:




 


static struct address_space_operations ramfs_aops = {    readpage: simple_readpage, // 读文件页块   prepare_write: simple_prepare_write,  //准备写文件页   commit_write: simple_commit_write    //将页设置为脏,完成提交写  };


; 函数simple_readpage对没有更新标识的页清空,对有更新标识的页不做任何操作,函数分析如下(在fs/libfs.c中): 


int simple_readpage(struct file *file, struct page *page)  {   void *kaddr;    //页面有PG_uptodate更新标识   if (PageUptodate(page))    goto out;      kaddr = kmap_atomic(page, KM_USER0);//   memset(kaddr, 0, PAGE_CACHE_SIZE);//设置kaddr开始的1页空间为0   kunmap_atomic(kaddr, KM_USER0);   flush_dcache_page(page); //函数为空   SetPageUptodate(page); //标记为更新  out:   unlock_page(page);   return 0;  }     int simple_commit_write(struct file *file, struct page *page,     unsigned offset, unsigned to)  {   struct inode *inode = page->mapping->host;    //算出位置(byte),index是在文件中的页序号,to表示页终止偏移,   loff_t pos = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;      //这儿不需要i_size_read函数,i_size不可能改变因为持有i_sem   if (pos > inode->i_size)//如果位置大于文件的尺寸    i_size_write(inode, pos);//即 inode->i_size = pos;   set_page_dirty(page);//设置页为脏   return 0;  }


 








目录及节点操作函数集 
ramfs_dir_inode_operations操作函数结构如下,这里只对其中的几个函数加以分析。 


static struct inode_operations ramfs_dir_inode_operations = {   .create  = ramfs_create,   .lookup  = simple_lookup,   .link  = simple_link,   .unlink  = simple_unlink,   .symlink = ramfs_symlink,   .mkdir  = ramfs_mkdir,   .rmdir  = simple_rmdir,   .mknod  = ramfs_mknod,   .rename  = simple_rename,  };     static int ramfs_mkdir(struct inode * dir, struct dentry * dentry, int mode)  {   return ramfs_mknod(dir, dentry, mode | S_IFDIR, 0);  }     static int ramfs_create(struct inode *dir, struct dentry *dentry, int mode)  {   return ramfs_mknod(dir, dentry, mode | S_IFREG, 0);  }


 




ramfs_mknod函数在VFS中创建一个节点,并将节点与dentry关联起来,这个函数分析如下:




 


static int ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int dev)  {   struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev);    int error = -ENOSPC;   if (inode) {    d_instantiate(dentry, inode);//将目录项dentry与文件描述inode相关联    dget(dentry);      error = 0;   }   return error;  }


ramfs_unlink函数删除目录dir中的空目录dentry,函数分析如下: 


static int ramfs_unlink(struct inode * dir, struct dentry *dentry)  {   int retval = -ENOTEMPTY;   // 在目录dir内删除dentry所表示的目录项   if (ramfs_empty(dentry)) {// 只能删除空目录    struct inode *inode = dentry->d_inode;    inode->i_nlink--;    dput(dentry);       retval = 0;   }   return retval;  }


 




ramfs_symlink函数建立链接文件,函数分析如下:




 


static int ramfs_symlink(struct inode * dir, struct dentry *dentry, const char *symname)  {   int error;   // 在目录dir中创建名称为symname的符号链接文件目录项   // 符号链接的名称作为符号链接文件的数据体存放在page cache中   error = ramfs_mknod(dir, dentry, S_IFLNK | S_IRWXUGO, 0);   if (!error) {    int l = strlen(symname)+1;    struct inode *inode = dentry->d_inode;    error = block_symlink(inode, symname, l);   }   return error;  }


proc文件系统 


proc文件系统是个虚拟文件系统,它通过文件系统接口实现对内核的访问,输出系统运行状态。它以文件系统的形式,为操作系统本身和应用进程之间的通信提供了一个界面,使应用程序能够安全、方便地获得系统当前的运行状况和内核的内部数据信息,并且可以修改某些系统的配置信息。比如能通过拥有模块列表的/proc/modules和拥有内存使用统计信息的/proc/meminfo来得到模块信息和内存信息。




proc文件系统在调试中的作用 


使用proc文件系统获得操作系统运行态的信息,对于进程和内核的调试都有很多好处。下面是使用/proc文件系统的优点:


可以直接使用/proc文件系统小的文件,开发—些专用程序获取内核数据,如ps就是通过从/proc文件系统获得数据的方式工作的。不需要将进程从用户态圳换到内核态,就能得到对内核运行空间的数据,安全而且方便。


另外,/proc文件系统可直接更改一些允许改变的内核参数数据(/proc/sys),从而可以在不重新编译内核的情况下优化系统配置。




/proc根下的文件和目录 


下面列表给出的是在/proc根日录下的文件和目录描述(按字母次序),以数字表示的与进程相关目录在另外一张表中描述。当然,有些目录和文件内核配置情况而定,并不一定存在。


表9-1:/proc根下文件和目录


文件/目录名   描 述


apm 高级电源管理信息


bus 包含了总线以及总线上设备信息的目录,子目录以总线类型组织


cmdline 内核的命令行参数


cpuinfo CPU信息,包括主频、类型等信息


devices 系统字符和块设备


dma 正在使用的DMA通道


filesystems 系统支持的文件系统类型


driver 组织了不同的驱动程序


execdomains 和安全相关的execdomain


fb    framebuffer设备


fs 文件系统需要的参数,对NFS/Export有效


ide 包含了IDE子系统信息的目录


interrupts 系统注册的中断信息


iomem 内存映像


ioports I/O端口使用情况


irq 与cpu相关的中断掩码


isapnp ISA PnP设备相关信息


kcore 内核的core文件映像


kmsg 内核消息


ksyms 内核符号表


loadavg 最近1分、5分、15分钟时候的平均装载量


locks 内核锁


meminfo 内存信息


misc 杂项信息


modules 系统正在使用模块的信息


mounts 已经装载的文件系统


net 保存网络信息的目录


partitions硬盘分区情况


pci PCI总线上设备情况


rtc 实时钟(RealTimeClock)


scsi SCSI设备信息


slabinfo Slab池信息


stat 静态统计信息


swap 交换分区的使用情况


sys 可以更改的内核数据的目录


sysvipe 和SysV IPC相关的数据文件


tty 和终端相关的数据


uptime 从系统启动到现在的时间


version 内核版本数据


video Video子系统的bttv信息


在/proc/目录下还有以数字作为目录名的目录,这些数字代表的含义是进程号。每一个系统当前运行的进程都会在这里出现和它对应的一个目录。另外还有一个self目录,是当前系统正在激活的进程目录的链接。可用如下命令查看当前进程状态: 


[root@Linux/proc]$cat self/status  Name:    cat  State: R (running)  Pid: 471  PPid:       447  Uid: 500   500 500   500  Gid: 5(X)  500 500  500  Groups: 500  VmSize:     1240 kB  VmLck:     0 kB  VmRSS.     440 kB  VmData:    24 kB  VmStk:     20 kB  VmExe:    8 kB  VmLib:     1024 kB  SigPnd:    O00O000000000000  Siglgn:    8000000000000000  SigCgt:    00000000fffffeff  CapEff:    00000000000000000  CapPrm:   00000000000000000  CapEff:   00000000000000000


 




在每个目录下,进程信息是类似的。表说明了/proc文件系统中进程相关目录的内容。




/proc文件系统应用举例 


/proc文件系统可以用于系统的调试。比如说/proc下的文件kcore,是当前内核的映像,那么可以通过kcore作为获取当前内核数据的环境,动态获得内核运行数据。Kcore目录下的文件或目录的内容描述如下表:


表9-2:进程相关文件/目录描述


文件/目录名 描 述


cmdline 该进程的命令行参数


cpu 当前和上次运行该进程的CPU号(SMP)


cwd 进程运行的当前路径的符号链接


environ 该进程运行的环境变量


exe 该进程相关的程序文件的符号链接


fd 包含了所有该进程使用的文件的文件描述符的目录


maps 可执行程序或者库文件对应的内存映像


mem 该进程使用的内存


root 该进程所有者的根目录


stat 进程状态


statm 进程的内存状态


status 用易读的方式表示的进程状态


/proc/sys目录是一个特殊的目录,它支持直接使用文件系统的写操作,完成对内核中预定的一些变量的改变,从而达到更改系统特性的目的。例如说需要增加系统同时打开文件的个数,以提高使用Linux作为文件服务器的性能,那么可以使用下面的语句:


[root@Linux proc]#ls /proc/sys/fs/file-max


4096


[root@Linuxproc]#echo 8192>/proc/sys/fs/file-max


[root@Linuxproc]#ls /proc/sys/fs/file-max


8192


另外,还有很多关于网络、文件系统的性能微调都是在/proc/sys/下的对应目录中完成的。




/proc文件系统实现分析 
数据结构定义 


????




数据结构proc_dir_entry 


在/proc文件系统中,代表各个文件节点的结构是proc_dir_entry{}结构。和文件系统中的dir_entry{}相似,它管理着从操作系统的用户空间到核心空间对文件读写的驱动。但是,和一般的文件系统不同的是,它修改的并不是实实在在的硬盘上的文件,而是在系统启动之后内存中由内核动态创建的文件。因此在系统关闭之后,/proc文件系统中的文件就不存在了。


  系统启动之后,创建了由proc_dir_entry{}结构形成的文件系统树,每当从用户空间读取/proc目录下面的文件的时候,内核根据读取的文件映射到对应的驱动函数,动态地获取内核数据。除了提供读的功能,/proc文件系统的部分文件还提供写的功能。主要是针对/proc/sys目录而做的。对/proc文件系统的写操作并不意味着需要写硬盘等硬件设备,而是动态更改内核中的数据,达到完成监视内核运行状态的目的,也就是部分地实现调试的功能。


/proc文件系统的超级块(Super Block)和普通文件系统的超级块不同,它并不需要从硬件设备中获取超级块的数据,而是在内核启动的时候直接初始化超级块数据,从而完成系统中对/proc文件系统操作函数的初始化过程,以及对这种文件系统的统计过程。
数据结构定义(include/linux/proc_fs.h)




 


struct proc_dir_entry {//描述/proc文件系统目录结构   unsigned short low_ino;//表示的是inode节点中成员i_ino的低16位   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; //保存和该proc_dir_entry{}相关的数据   read_proc_t *read_proc;   write_proc_t *write_proc;   atomic_t count;  //使用计数,表示有多少个使用者正在使用        //删除标识,以便在申请新的proc_dir_entry{}时重使用这个结构,         //以达到申请内存空间的时间。   int deleted;     kdev_t rdev;//存放了这个文件的主设备号(低8位)和次设备号(高8位)  };


 


对/proc的root来说,inode号的低16位为1;而对/proc的以系统当前进程号为目录名的目录来说,这些目录的inode号的低16位为2,高16位为进程号,从而保证不同的进程的/proc文件系统的inode与之对应。


指针data是一个void类型的指针,用于保存一个可能会和该proc_dir_entry{}相关的内存。例如,对一个proc中做了符号连接的文件来说,data中保存的是连接文件的文件名。如果是对/proc/sys目录下的文件,那data中保存的是和它对应的ctl_table{}结构指针。


proc_dir_entry{}是用来描述一个/proc文件系统中目录结构节点的。每一个节点在整个目录结构中或者是一个文件,或者是一个目录,通过一些指针将大量的proc_dir_entry{}节点组成树状结构。该结构标记了一个目录结构,提供了对文件的内容的读写所需要的函数指针。 


struct proc_dir_entry {   unsigned short low_ino;   unsigned short namelen;   const char *name;   mode_t mode;            //指inode是否目录,是否可读等   nlink_t nlink;         //是指该目录下子目录的数目   uid_t uid;            //用户号   gid_t gid;            //组号   unsigned long size;   struct inode_operations * proc_iops; //针对结点inode{}的操作函数   struct file_operations * proc_fops;  //针对file{}的操作函数   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;         //使用计数   int deleted;     //删除标识,表示一个结构实例可被再使用         //用于/proc文件系统的设备文件,存放了这个文件的主设备号和次设备号,          //分别在低8位和高8位中。   kdev_t rdev;   };


 




结构中两个操作函数指针proc_iops和proc_fops随目录的不同而不同,对于整个/proc文件系统的根proc_root,它们分别指向proc_root_inode_operation和proc_root_operations;对/proc的二级子目录(root部分)读取的话,就直接proc_dir_operations/proc_dir_inode_operaions、proc_link_inode_operations或者proc_file_operations。如果是base部分(与进程相关的目录部分)的话,调用proc_base_operations和proc_base_inode_operations。


Linux special filesystem 03 1024.png


    图 9 2:proc_dir_entry{}结构的节点组成的树状和链状结构




/proc文件系统的初始化 
(1)文件系统注册 


/proc文件系统通过函数proc_root_init向VFS注册的。函数proc_root_init还挂接/proc文件系统,并初始化/proc下的目录。函数分析如下(在fs/proc/root.c中):




 


void __init proc_root_init(void)  {    //创建proc_inode结构的slab缓存   int err = proc_init_inodecache();   if (err)    return;      //注册proc_fs_type类型文件系统   err = register_filesystem(&proc_fs_type);   if (err)    return;   proc_mnt = kern_mount(&proc_fs_type); //挂接/proc文件系统   err = PTR_ERR(proc_mnt);   if (IS_ERR(proc_mnt)) {//挂接失败,取消/proc文件系统    unregister_filesystem(&proc_fs_type);    return;   }   proc_misc_init();//初始化硬件信息相关的文件或文件夹   proc_net = proc_mkdir("net", NULL);   proc_net_stat = proc_mkdir("net/stat", NULL);  //创建/proc下的各种目录  #ifdef CONFIG_SYSVIPC   proc_mkdir("sysvipc", NULL);  #endif  #ifdef CONFIG_SYSCTL   proc_sys_root = proc_mkdir("sys", NULL);  #endif  #if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE)   proc_mkdir("sys/fs", NULL);   proc_mkdir("sys/fs/binfmt_misc", NULL);  #endif   proc_root_fs = proc_mkdir("fs", NULL);   proc_root_driver = proc_mkdir("driver", NULL);   proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */  #if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)   /* just give it a mountpoint */   proc_mkdir("openprom", NULL);  #endif   proc_tty_init();//初始化tty子目录树  #ifdef CONFIG_PROC_DEVICETREE   proc_device_tree_init();//初始化设备目录树  #endif   proc_bus = proc_mkdir("bus", NULL);  }


 


文件系统类型proc_fs_type结构实例列出如下:




 


static struct file_system_type proc_fs_type = {   .name  = "proc",   .get_sb  = proc_get_sb,   .kill_sb = kill_anon_super,  };


  函数proc_get_sb得到超级块,函数列出如下: 


static struct super_block *proc_get_sb(struct file_system_type *fs_type,   int flags, const char *dev_name, void *data)  {   return get_sb_single(fs_type, flags, data, proc_fill_super);  }


函数proc_fill_super初始化超级块,这里使用固定的数据将超级块初始化。FS_SINGLE表示只有一个超级块。/proc文件系统超级块和普通文件系统不同,它并不需要从硬件设备中获取超级块的数据,因为它仅存在内存中。 函数proc_read_supper分析如下(在fs/proc/inode.c中): 


struct super_block *proc_fill_super (struct super_block *s,void *data,           int silent)  {   struct inode * root_inode;   struct task_struct *p;      s->s_blocksize = 1024;   s->s_blocksize_bits = 10;   s->s_magic = PROC_SUPER_MAGIC;   s->s_op = &proc_sops;  //超级块操作函数实例   //查找节点号 PROC_ROOT_INO = 1的节点结构,若没有,创建一个,  //用根节点的proc_root结构实例的数据填充它   root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);   if (!root_inode)    goto out_no_root;   /*    * Fixup the root inode's nlink value    */   read_lock(&tasklist_lock);    //i_nlink表示包含的文件和目录的个数,/proc根目录下每个进程有一个目录   for_each_task(p) if (p->pid) root_inode->i_nlink++;   read_unlock(&tasklist_lock);   s->s_root = d_alloc_root(root_inode);//由结点生成dentry{}结构   if (!s->s_root)    goto out_no_root;    //分析data中uid及gid值并填充到结点结构中去   parse_options(data, &root_inode->i_uid, &root_inode->i_gid);   return s;     out_no_root:   printk("proc_read_super: get root inode failed\n");   iput(root_inode);//移去结点   return NULL;  }


(2) /proc根结点proc_root 


上一节函数proc_fill_supper()向超级块填充了几个结构,实际上是向VFS填充根结点的一些数据结构。超级块的操作函数定义如下:


在fs/proc/inode.c中定义了操作结构proc_sops的实现: 


static struct super_operations proc_sops = {    read_inode: proc_read_inode,   put_inode: force_delete,   delete_inode: proc_delete_inode,   statfs:  proc_statfs,  };


在fs/proc/root.c中定义了/proc文件系统的根节点,根目录的结构如下: 


struct proc_dir_entry proc_root = {  
 low_ino: PROC_ROOT_INO,    namelen: 5,    name:  "/proc",   mode:  S_IFDIR | S_IRUGO | S_IXUGO,    nlink:  2,    proc_iops: &proc_root_inode_operations,    proc_fops: &proc_root_operations,   parent:  &proc_root,  


  };


以下是根目录两个操作函数集实例,一个节点操作函数集实例proc_iops,另一个是根文件操作函数集实例,分别列出如下(在fs/proc/root.c中): 


static struct inode_operations proc_root_inode_operations = {  
 lookup:  proc_root_lookup,  };  static struct file_operations proc_root_operations = {   read:   generic_read_dir,   readdir:  proc_root_readdir,  


  };


由于对于节点操作函数集实例proc_root来说,它对应的文件具有目录的属性,但任何用户没在直接写的权限。因而read函数无用,generic_read_dir只是返回一个错误码,而仅readdir函数有用。 
3. /proc下各文件和目录的初始化 


/proc文件系统主要分成两种部分,一部分和进程相关的目录部分,在实现的时候将这部分称为base部分;另一部分是把/proc根下面的其他目录和文件,又分为两部分,一是/proc下的子目录,另一是/proc下的文件,如cpuinfo、kmsg等。这三部分分别调用不同的初始化函数完成初始化的。其中/proc下的文件,如cpuinfo、kmsg等是由proc_misc_init()初始化的。


函数proc_misc_init给每个文件或文件夹创建proc_dir_entry结构,并赋上了它自己的操作函数集,函数proc_misc_init分析如下(在/proc/proc_misc.c中): 


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[] = {    ……    {"meminfo", meminfo_read_proc},    ……  #ifdef CONFIG_MODULES    {"modules", modules_read_proc},  #endif    {"stat", kstat_read_proc},    {"devices", devices_read_proc},          ……    {NULL,}   };    //创建每个文件名对应的proc_dir_entry结构,并将设置的操作函数赋上   for (p = simple_ones; p->name; p++)    create_proc_read_entry(p->name, 0, NULL, p->read_proc, NULL);  ……  //创建名为mounts的proc_dir_entry结构,  //并将self/mounts字符拷贝到proc_dir_entry结构data成员上   proc_symlink("mounts", NULL, "self/mounts");      /* And now for trickier ones */     //在proc_root下即根目录下创建名为kmsg的目录,并赋上操作函数集   entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);   if (entry)    entry->proc_fops = &proc_kmsg_operations;   create_seq_entry("cpuinfo", 0, &proc_cpuinfo_operations);   ……  #ifdef CONFIG_MODULES      //创建名字ksyms的文件的proc_dir_entry结构,     //并赋上文件操作函数集proc_ksyms_operations   create_seq_entry("ksyms", 0, &proc_ksyms_operations);  #endif   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;   }   ……   }  }


4. 对/proc根目录下各文件和目录的操作 


对于根目录下的文件主要有三种类型,一种是目录,操作函数对应于proc_dir_operations和proc_dir_inode_operations;第二种是符号链接函数,它只有inode的操作,操作函数只有proc_link_inode_operations;第三种是普通/proc文件,操作函数只有proc_file_operations。初始化对应的成员函数列于下表:


名称 类别 成员函数
proc_dir_operation struct file_operationsread:generic_read_dir
readdir:proc_readdir
proc_dir_inode_operations struct inode_operationslookup:proc_lookup
proc_link_inode_operations struct inode_operationsreadlink:proc_readlink
followlink:proc_follow_link
proc_file_operations struc file_operationsllseek:proc_file_lseek
read:proc_file_read
write:proc_file_write
表9-3:具体的文件和目录操作函数
当使用ls命令查看一个目录下有什么文件时,是使用glibc库中定义的reagdir()函数获得一个目录下的文件的数据的,在内核的文件系统的实现中,就是使用各种文件系统自己定义的readdir()函数来获取的,在/proc系统中是使用proc_readdir()函数来获取的。


函数struct dentry *proc_lookup(struct inode * dir, struct dentry *dentry)是根据文件或目录对应的proc_dir_entry{}结构,在目录结构树的孩子中查找与dentry具有相同名字的一个proc_dir_entry{}结构。如果找到,那么将这个节点对应的inode和dentry相关联,给一个dentry填充inode信息,并加回到hash表中,且返回NULL,否则返回错误信息。




在/proc中读写设备信息示例 
示例1.如何在/proc中读设备信息 


  使用proc文件系统的方法与使用设备驱动一样:创建一个数据结构,使之包含/proc文件需要的全部信息,包括所有函数的句柄。然后,用init_module注册这个结构,用cleanup_module注销。


  使用proc_register的原因是我们不希望决定以后在文件中使用的索引节点数,而是让内核来决定它,为了防止冲突。标准的文件系统是在磁盘上而不是在内存(/proc的位置在内存)。


下面是一个样例程序procexamp.c,它在/proc中创建一个文件,存放数据到proc文件系统的文件中。如果使用自己的buffer,就把它的位置放在第二个参数中,并返回buffer中使用的字节数,0返回值意味着文件结束,没有更多消息,返回负值意味着是一个错误状态。样例如下:




 


//内核模块标准头文件  #include <linux/kernel.h>     #include <linux/module.h>   //内核模块相关定义  #include <linux/proc_fs.h>     int procfile_read(char *buffer, //内核分配的buffer      char **buffer_location, //存放数据的地方,指向自定义的buffer      off_t offset,//文件偏移       int buffer_length, //第一个参数buffer的数据长度      int zero)  {    int len;  // 实际使用的字节数       //静态的,离开此函数后,它还在内存中可用    static char my_buffer[80];         static int count = 1;    if (offset > 0)      return 0;       //填充buffer并得到其长度    len = sprintf(my_buffer,       "For the %d%s time, go away!\n", count,      (count % 100 > 10 && count % 100 < 14) ? "th" :         (count % 10 == 1) ? "st" :          (count % 10 == 2) ? "nd" :            (count % 10 == 3) ? "rd" : "th" );    count++;    //my_buffer是静态的,可以传出到函数外   *buffer_location = my_buffer;        return len;  }     struct proc_dir_entry Our_Proc_File =     {      0, //节点号-忽略,它将被proc_register[_dynamic]填充      4, //文件名长度      "test", //文件名      S_IFREG | S_IRUGO, //文件模式 – 被任何人读      1, //链接数      0, 0,  //文件的 uid 和 gid – 这里为 root      80, //文件用ls命令显示的大小      NULL, //节点上操作函数如link、remove      procfile_read, //此文件的读函数      NULL     };      int init_module()  {     return proc_register(&proc_root, &Our_Proc_File);    // proc_root是proc文件系统的根目录,文件放在/proc下  }     void cleanup_module()  {    proc_unregister(&proc_root, Our_Proc_File.low_ino);  }


示例2.使用/proc进行读写 


  proc文件系统主要是为满足内核向进程报告其状态的,没有为输入留出特别的机制。数据结构proc_dir_entry没有包含一个指向某个输入函数的指针。如果要向一个/proc文件写入,只能使用标准文件系统机制。


  文件系统注册的标准机制是每个文件系统都有自己的函数来处理索引节点和文件操作,struct inode_operations结构中有一个指向struct file_operations的指针。在/proc里,可在file_operations里包含我们的module_input和module_output函数。


  下面是一个样例proceamp.c,它在/proc下创建名为"file"的文件,并可对它进行读写操作,它的实现如下:




 


#include <linux/kernel.h>     #include <linux/module.h>     #include <linux/proc_fs.h>     #include <asm/uaccess.h>  /* for get_user and put_user */     //这里保存最后一次收到的消息  #define MESSAGE_LENGTH 80  static char Message[MESSAGE_LENGTH];     //因为使用文件操作结构,必须使用标准的读函数,即下面函数  static ssize_t module_output(      struct file *file,   //读取的文件      char *buf, //用户空间存放数据的 buffer       size_t len,  //buffer的长度      loff_t *offset) //文件的偏移-忽略  {    static int finished = 0;    int i;    char message[MESSAGE_LENGTH+30];       //返回0表示文件结束,    if (finished) {      finished = 0;      return 0;    }       //将字符串从内核空间拷贝到用户空间    sprintf(message, "Last input:%s", Message);    for(i=0; i<len && message[i]; i++)       put_user(message[i], buf+i);       finished = 1;        return i;  //返回读取的字节数  }


 


当用户写入/proc文件系统中的文件时,这个函数从用户空间接收数据,




 


static ssize_t module_input(      struct file *file,   //文件      const char *buf,     //用户空间buffer,用于输入数据      size_t length,       //buffer长度      loff_t *offset)      //文件偏移  {    int i;       //从用户空间得到数据到Message中    for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)      get_user(Message[i], buf+i);       Message[i] = '\0';  //加上标准0结束符     return i;  }     //判断是否允许操作:0-允许 非0-不允许    * The operation can be one of the following values:   * 0 - Execute    * 2 - Write    * 4 - Read   static int module_permission(struct inode *inode, int op)  {    //允许每个人读但仅root用户(uid=0)写     if (op == 4 || (op == 2 && current->euid == 0))      return 0;      return -EACCES;  }     //模块引用计数  int module_open(struct inode *inode, struct file *file)  {    MOD_INC_USE_COUNT;       return 0;  }     int module_close(struct inode *inode, struct file *file)  {    MOD_DEC_USE_COUNT;       return 0;  /* success */  }     //这个文件的文件操作函数集  static struct file_operations File_Ops_4_Our_Proc_File =    {      NULL,  // lseek函数      module_output,  //读文件函数      module_input,   //写文件函数      NULL,  /* readdir */      NULL,  /* select */      NULL,  /* ioctl */      NULL,  /* mmap */      module_open,    //打开文件函数      NULL,   // flush函数      module_close,    //close函数      //其它定义的将使用缺省函数    };     //节点操作函数  static struct inode_operations Inode_Ops_4_Our_Proc_File =    {      &File_Ops_4_Our_Proc_File,      NULL, /* create */      NULL, /* lookup */      NULL, /* link */      NULL, /* unlink */      NULL, /* symlink */      NULL, /* mkdir */      NULL, /* rmdir */      NULL, /* mknod */      NULL, /* rename */      NULL, /* readlink */      NULL, /* follow_link */      NULL, /* readpage */      NULL, /* writepage */      NULL, /* bmap */      NULL, /* truncate */      module_permission /* check for permissions */    };     //目录entry结构/  static struct proc_dir_entry Our_Proc_File =     {      0, //忽略节点号      7, //文件名长度      "rw_test", //文件名      S_IFREG | S_IRUGO | S_IWUSR,       // File mode – 常规文件,每个用户可读,拥有者可写      1,  //链接数      0, 0,  //uid和 gid设置为root       80, //用ls命令看到的文件大小      &Inode_Ops_4_Our_Proc_File,       //文件的节点结构      NULL        //文件读函数,已把读函数放在节点结构中,这里不需要    };      int init_module()  {      return proc_register(&proc_root, &Our_Proc_File);  }     void cleanup_module()  {    proc_unregister(&proc_root, Our_Proc_File.low_ino);  }


示例3. 用户空间使用/proc控制设备 


在用户空间,/proc文件系统的文件,可以象普通文件一样读写,这样可通过对/proc文件系统的文件的读写达到对驱动程序或其它内核的控制。下面是一个对usb总线下特殊设备进行切换控制的例子,它就是通过读写/proc文件系统的文件来达到控制的目的。




 


#define USBD_PROCFILE  "/proc/usbd"  #define USB_STATUS_MODEM 0  #define USB_STATUS_NET  1  #define USB_STATUS_STRING_MODEM  "modemStatus"  #define USB_STATUS_STRING_NET  "NetStatus"  static int SetUsbSwitchInterface(int switchmode)  {   int rc;   FILE *usbd_fd_switch;         //以写的方式打开文件"/proc/usbd"      if (NULL == (usbd_fd_switch = fopen(USBD_PROCFILE,"w")))      {          return -1;      }         /*Switch mode by parameter*/      if(switchmode == USB_STATUS_MODEM)      {          // 设置USB连接到 modem          rc = fputs(USB_STATUS_STRING_MODEM, usbd_fd_switch);          if (rc < 0)          {              fclose(usbd_fd_switch);              return -1;          }      }      else if(switchmode == USB_STATUS_NET)      {//设置USB连接到网络          rc = fputs(USB_STATUS_STRING_NET, usbd_fd_switch);          if (rc < 0)          {              fclose(usbd_fd_switch);              return -1;          }      }         fclose(usbd_fd_switch);      return 0;  }     static int  GetUsbSwitchInterface()  {   char read[32];   char *str;    FILE *usbd_fd;   int len;         QString msg;      //以读的方式打开文件"/proc/usbd"   if (NULL == (usbd_fd = fopen(USBD_PROCFILE,"r"))) {    return USB_STATUS_NONE;   }     memset(read, 0, 32);  //读取32个字节   fgets(read, 32, usbd_fd);      fclose(usbd_fd);      len = strlen(read);      read[len-1] = 0;      //比较read字符串是否含特定的字符串      if(!strncmp(read,USB_STATUS_STRING_NONE, trlen(USB_STATUS_STRING_NONE)))           return USB_STATUS_NONE;  else if(!strncmp(read, USB_STATUS_STRING_NET,                                strlen(USB_STATUS_STRING_NET)))          return USB_STATUS_NET;  else if(!strncmp(read, USB_STATUS_STRING_MODEM,                                 strlen(USB_STATUS_STRING_MODEM)))          return USB_STATUS_MODEM;      return USB_STATUS_NONE; //没发现设备  }




Currently 4.50/5

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值