Linux内核入门到放弃-虚拟文件系统-《深入Linux内核架构》笔记
VFS的任务并不简单。一方面,它用来提供了一种操作文件、目录及其他对象的统一方法。另一方面,它必须能够与各种方法给出的具体文件系统的实现达成妥协,这些实现在具体细节、总体设计方面都有一些不同之处。
文件系统类型
- 基于磁盘的文件系统
- 虚拟文件系统
- 网络文件系统
通用文件模型
在处理文件时,内核空间和用户空间使用的主要对象是不同的。对用户程序来说,一个文件由一个文件描述符标识。内核处理文件的关键是inode。
inode
目录只是一个特殊的文件。
inode的成员可能分为下面两类。
- 描述文件状态的元数据。
- 保存实际文件内容的数据段。
为阐明如何用inodes来构造文件系统的目录层次结构,我们来考察内核查找对应于/usr/bin/emacs的inode过程。
查找起始于inode,它表示根目录/,对系统来说必须总是已知的。该目录由一个inode表示,其数据段并不包含普通数据,而是根目录下的各个目录项。这些目录项可能代表文件或其他目录。每个项由两个成员组成。
- 该目录项的数据所在inode的编号
- 文件或目录的名称
链接
对每个符号链接都使用了一个独立的inode。相应inode的数据段包含一个字符串,给出了链接目标的路径。
对于硬链接建立时,创建的目录项使用了给定文件的inode编号。
VFS结构
结构概观
VFS由两个部分组成,文件和文件系统,这些都需要管理和抽象。
文件表示
在抽象对底层文件系统的访问时,并未使用固定的函数,而是使用了函数指针。这些函数指针保存在两个结构中,包括了所有相关的函数。
- inode操作:创建链接、文件重命名、在目录中生成新文件、删除文件。
- 文件操作:作用于文件的数据内容。它们包含一些显然的操作(如读和写),还包括如设置文件指针和创建内存映射之类的操作。
每个inode还包含了一个指向底层文件系统的超级块对象的指针,用于执行inode本身的操作。
文件系统和超级块信息
超级块还包含了读、写、操作inode的函数指针。也包含了文件系统的关键信息(块长度、最大文件长度,等等)。
超级块结构的一个重要成员是一个列表,包括相关文件系统中所有修改过的inode(内核相当不敬地称之为脏inode???miao miao miao?)。根据该列表很容易标识已经修改过的文件和目录,以便将其写回到存储介质。
inode
<fs.h>
struct inode {
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;//inode编号标识
atomic_t i_count;//访问该inode结构的进程数目
unsigned int i_nlink;//硬链接总数
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev;//在inode表示设备文件时,使用
unsigned long i_version;
loff_t i_size;//文件长度,字节计算
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;//最后访问的时间,
struct timespec i_mtime;//最后修改的时间(数据内容)
struct timespec i_ctime;//最后修改inode的时间(inode内容)
unsigned int i_blkbits;
blkcnt_t i_blocks;//文件按块计算的长度
unsigned short i_bytes;
umode_t i_mode;//文件访问权限
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
int i_cindex;
__u32 i_generation;
#ifdef CONFIG_DNOTIFY
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
#endif
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endif
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned int i_flags;
atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
void *i_private; /* fs or device private pointer */
};
inode操作
file_operations用于操作文件中包含的数据,而inode_operations负责管理结构性的操作(例如删除一个文件)和文件相关的元数据(例如,属性)。
struct inode_operations {
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);//根据文件系统对象的名称。查找其inode实例
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);//删除文件
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,int,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*readlink) (struct dentry *, char __user *,int);
void * (*follow_link) (struct dentry *, struct nameidata *);//根据符号链接查找目录的inode
void (*put_link) (struct dentry *, struct nameidata *, void *);
void (*truncate) (struct inode *);
int (*permission) (struct inode *, int, struct nameidata *);
int (*setattr) (struct dentry *, struct iattr *);//xattr函数,用于建立、读取、删除文件的扩展属性
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*removexattr) (struct dentry *, const char *);
void (*truncate_range)(struct inode *, loff_t, loff_t);//截断一个范围内的块
long (*fallocate)(struct inode *inode, int mode, loff_t offset,
loff_t len);//用于对文件预先分配空间
};
inode链表
每个inode都有一个i_list成员,可以将inode存储在一个链表中。根据inode的状态,它可能有3种主要的情况。
- inode处于内存中,未关联到任何文件,也不处于活动使用状态
- inode结构在内存中,正在由一个或多个进程使用,通常表示一个文件。两个计数器(i_count和i_nlink)的值都必须大于0。文件内容和inode元数据都与底层块设备上的信息相同。也就是说,与上一次与存储介质同步以来,该inode没有改变过
- inode处于活动使用状态。其数据内容已经改变,与存储介质上的内容不同。这种状态的inode被称作脏的。
在fs/inode.c中内核定义了两个全局变量用作表头,inode_unsued用于有效但非活动的inode。inode_in_use用于所有使用但未改变的inode。脏的inode保存在一个特定于超级块的链表中(super_block->s_dirty)。
每个inode不仅出现在特定于状态的链表中,还在一个散列表中出现(inode_hashtable,也定义在fs/inode.c中),以根据inode编号和超级快快速访问inode。
inode还通过一个特定于超级块的链表维护,表头是super_block->s_inodes。i_sb_list用作链表元素。
表头为super_block->s_io和super_block->s_more_io使用同样的链表元素i_list。这两个链表包含的是已经选中向磁盘回写的inode,但正在等待回写进行。
特定于进程的信息
<sched.h>
struct task_struct {
...
/* 文件系统信息 */
int link_count, total_link_count;
...
/* 文件系统信息 */
struct fs_struct *fs;
/* 打开文件信息 */
struct files_struct *files;
/* 命名空间 */
struct nsproxy *nsproxy;
...
}
<file.h>
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
//fdtable在打开超过NR_OPEN_DEFAULT个文件时,使用
struct fdtable *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;//表示下一次打开新文件时使用的文件描述符
struct embedded_fd_set close_on_exec_init;//位图,对执行exec时将关闭的所有文件描述符,都置位。
struct embedded_fd_set open_fds_init;//最初文件描述符集合
struct file * fd_array[NR_OPEN_DEFAULT];//指向每个打开文件的struct file实例(默认情况下,内核允许每个进程打开NR_OPEN_DEFAULT个文件)
};
如果进程试图打开更多的文件(大于NR_OPEN_DEFAULT),则内核需要分配更多的内核空间。fdtable用于该目的。
struct fdtable {
unsigned int max_fds;//max_fds指定了进程当前可以处理的文件对象和文件描述符的大数目
struct file ** fd; /* current fd array */
fd_set *close_on_exec;//是一个指向位域的指针,该位域保存了所有在exec系统调用时将要关闭的文件描述符的信息
fd_set *open_fds;//是一个指向位域的指针,该位域管理着当前所有打开文件的描述符
struct rcu_head rcu;
struct fdtable *next;
};
开始,fdt指向fdtab,fdtab中的成员fd、open_fds和 close_on_exec都初始化为指向前者对应的3个成员。
struct file。该结构保存了内核所看到的文件的特征信息。
<fs.h>
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;//封装了两部分信息:1.文件名和inode之间的关联;2.文件所在文件系统的有关信息。
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;//文件操作函数
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;//文件操作模式
loff_t f_pos;//文件指针
struct fown_struct f_owner;//包含了处理该文件的进程有关的信息
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;//预读
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;//指向属于文件相关的inode实例的地址空间映射
};
每个超级块都提供了一个s_list成员用作表头,以建立file对象的链表,链表元素是 file->f_list。该链表包含该超级块表示的文件系统的所有打开文件。
file实例可以用get_emtpy_filep分配,该函数利用了自身的缓存并将实例用基本数据预先初始化。
每当内核打开一个文件或做其他的操作时,如果需要file_struct提供比初始值更多的项,则调用expand_files函数。
文件操作
各个file实例都包含一个指向struct file_operations实例的指针,该结构保存了指向所有可能文件操作的函数指针。
<fs.h>
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//异步读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);//读取目录内容
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);//打开一个文件,相当于将file对象关联到inode
int (*flush) (struct file *, fl_owner_t id);//在文件描述符关闭时调用,同时将file对象计数减1
int (*release) (struct inode *, struct file *);//file对象为0时调用
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
目录信息
每个task_struct实例都包含一个指针,指向另一个结构,类型为fs_struct.
<fs_struct.h>
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;//用于设置新文件的权限
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
- root和rootmnt指定了相关进程的根目录和文件系统
- pwd和pwdmnt指定了当前工作目录和文件系统。pwdmnt只有进入了一个新的装载点时,才会改变
- altroot和altrootmnt成员用于实现个性(personality)。这种特性允许为二进制程序建立一个仿真环境,使得程序认为是在不同于Linux的某个操作系统下运行。例如,在Sparc系统上仿真SunOS时就使用了该方法。仿真所需的特殊文件和库安置在一个目录中(通常是/usr/gnemul/)。有关该路径的信息保存在alt成员中。在搜索文件时总是优先扫描上述目录,因此首先会找到仿真的库或系统文件,而不是Linux系 统的文件(这些之后才搜索)。这支持对不同的二进制格式同时使用不同的库
VFS命名空间
VFS命名空间是所有已经装载、构成某个容器目录树的文件系统的集合。
内核使用以下结构管理命名空间。在各种命名空间中,其中之一是VFS命名空间。
<nsproxy.h>
struct nsproxy{
...
struct mnt_namespace *mnt_ns;
...
}
<mnt_namespace.h>
struct mnt_namespace{
atomic_t count;//使用该命名空间的进程数目
struct vfsmount *root;
struct list_head list;//VFS命名空间中所有文件系统的vfsmount实例
...
}
目录项缓存
dentry结构的主要用途是建立文件名和相关的inode之间的关联。
在VFS连同文件系统实现读取的一个目录项的数据之后,则创建一个dentry实例,以缓存找到的数据。
<dcache.h>
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock *///DCACHE_DISCONNECTED指定一个dentry当前没有连接到超级块的dentry树。DCACHE_UNHASHED表明该dentry实例没有包含在任何inode的散列表中。
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative *///文件名所属的inode,如果为NULL,则表示不存在的文件名
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory *///指向当前节点父目录的dentry实例(对于根目录指向自身)
struct qstr d_name; //指定了文件的名称,qstr是一个内核字符串的包装器,它存储了实际的char(只存储最后一个分量,如/usr/src,则存储src) *字符串以及字符串长度和散列值,这使得更容易处理查找工作。
struct list_head d_lru; /* LRU list */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* child of parent list *///用于将当前dentry链接到父目录dentry的d_subdirs链表中。
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children *///子目录/文件的目录项链表
struct list_head d_alias; /* inode alias list *///用于将dentry链接到inode的i_dentry链表中,以链接表示相同文件的各个dentry对象
unsigned long d_time; /* used by d_revalidate *///
struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;//当前dentry对象表示一个装载点,那么d_mounted设置为1,否则其值为0
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names *///短文件名(文件名只由少量字符组成时,才保存在d_iname中)
};
内存中所有活动的dentry实例都保存在一个散列表中,该散列表使用fs/dcache.c中的全局变量dentry_hashtable实现。用d_hash实现的溢出链,用于解决散列碰撞。在下文中,我将该散列表称 为全局dentry散列表。
内核中还有另一个dentry的链表,表头是全局变量dentry_unused(也在fs/dcache.c中初始化)。所有使用计数器(d_count)到达0(因而任何进程都不再使用)的 dentry实例都自动地放置到该链表上。
缓存组织
dentry对象在内存中的组织,涉及下面两个部分。
- 一个散列表,包含了所有dentry对象。
- 一个LRU链表,其中不再使用的对象将授予一个最后宽期限,宽期限过后才从内存移除。
在dentry对象的使用计数器(d_count)到达0时,会被置于LRU链表上,这表明没有什么应用程序正在使用该对象。新项总是置于该链表的起始处。换句话说,一项在链表中越靠后,它就越老,这是经典的LRU原理。prune_dcache会时常调用,例如在卸载文件系统或内核需要更多内存时。其中会删除比较老的对象,以释放内存。要注意,有时候dentry对象可能临时处于该链表上,尽管这些对象仍然处于活动使用状态,而且其使用计数大于0。这是因为内核进行了一些优化:在LRU链表上的dentry对象恢复使用时,不会立即将其从LRU链表移除,这可以省去一些锁操作,从而提高了性能。有些操作如prune_dcache,无论如何代价都比较高,我们可以对这种情况作出补救。具体地,如果遇到使用计数为正值的对象,只是将其从链表移除,而不释放该对象。
dentry操作
dentry_operations结构保存了一些指向各种特定于文件系统可以对dentry对象执行的操作的函数指针。
struct dentry_operations {
int (*d_revalidate)(struct dentry *, struct nameidata *);//检查内存中,各个dentry对象构成的结构是否仍然能够反映当前文件系统中的情况
int (*d_hash) (struct dentry *, struct qstr *);//计算散列值,该值用于将对象放置到dentry散列表中。
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);//比较两个dentry对象的文件名。
int (*d_delete)(struct dentry *);//再d_count到达0时,调用
void (*d_release)(struct dentry *);//再d_count到达0时,先于d_delete调用
void (*d_iput)(struct dentry *, struct inode *);//从一个不再使用的dentry对象中释放inode(默认情况下,将inode的使用计数减1,计数器到达0后,将inode从各种链表中移除)
char *(*d_dname)(struct dentry *, char *, int);
};
由于大多数文件系统都没有实现前述的这些函数,内核的惯例是这样:如果文件系统对每个函数 提供的实现为NULL指针,则将其替换为VFS的默认实现。
标准函数
以下辅助函数需要一个指向struct dentry的指针作为参数。
- 每当内核的某个部分需要使用一个dentry实例时,都需要调用dget。调用dget将对象的引用 计数加1,即获取对象的一个引用。
- dput是dget的对应物。如果内核中的某个使用者不再需要一个dentry实例时,就必须调用dput。该函数将dentry对象的使用计数减1。如果计数下降到0,则调用dentry_operations->d_delete方法(如果可用)。此外,还需要使用d_drop从全局dentry散列表移除该实例,并将其置于LRU链表上。
- d_drop将一个dentry实例从全局dentry散列表移除。
- d_delete在确认dentry对象仍然包含在全局dentry散列表中之后,使用__d_drop将其移除。如果该对象此时只剩余一个使用者,还会调用dentry_iput将相关inode的使用计数减1。
- d_instantiate将一个dentry实例与一个inode关联起来。这意味着设置d_inode字段并将该 dentry增加到inode->i_dentry链表。
- d_add调用了d_instantiate。此外,该对象还添加到全局dentry散列表dentry_hashtable 中。
- d_alloc为一个新的struct dentry实例分配内存。初始化各个字段,如果给出了一个表示父结点的dentry,则新dentry对象的超级块指针从父结点获取。
- d_alloc_anon为一个struct dentry实例分配内存,但并不设置与父结点dentry的任何关联, 因此该函数与d_alloc相比去掉了相关参数。
- d_splice_alias将一个断开连接的dentry对象连接到dentry树中。该功能的inode参数表示 与dentry关联的inode。
- d_lookup根据目录对应的dentry实例,搜索名称为name的文件对应的dentry对象。
处理VFS对象
文件系统操作
每个文件系统在使用以前必须注册到内核,这样内核都能够了解可用的文件系统,并按需调用装载功能。
注册文件系统
文件系统的表示:
<fs.h>
struct file_system_type {
const char *name;//文件系统的名称
int fs_flags;//使用的标志,例如标明只读装载、禁止setuid/setgid操作或进行其他的微调
int (*get_sb) (struct file_system_type *, int,
const char *, void *, struct vfsmount *);//从底层存储介质读取超级块
void (*kill_sb) (struct super_block *);//在不需要某个文件系统类型时执行清理工作。
struct module *owner;
struct file_system_type * next;//链接到下一个文件系统
struct list_head fs_supers;//由于可以装载几个同一类型的文件系统,同一文件系统类型可能对应了多个超级块结构,这些超级块结构,聚集在一个链表中
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
struct lock_class_key i_alloc_sem_key;
};
文件系统的注册函数,register_filesystem。
<fs/filesystems.c>
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
INIT_LIST_HEAD(&fs->fs_supers);
write_lock(&file_systems_lock);
p = find_filesystem(fs->name, strlen(fs->name));//查看准备注册的文件系统是否已经存在
if (*p)
res = -EBUSY;
else
*p = fs;//不存在,将其添加到file_systems文件系统链表上
write_unlock(&file_systems_lock);
return res;
}
装载和卸载
每个装载的文件系统都有一个本地根目录,其中包含了系统目录。在将文件系统装载到一个目录时,装载点的内容被替换为即将装载的文件系统的相对根目录的内容。前一个目录数据消失,直至新文件系统卸载才重新出现。
每个装载的文件系统都对应于一个vfsmount结构的实例,其定义如下:
<mount.h>
struct vfsmount {
struct list_head mnt_hash; //vfsmount实例的地址和相关的dentry对象的地址用来计算散列和。散列表,称作mount_hashtable,定义在fs/namespace.c中。
struct vfsmount *mnt_parent; /* fs we are mounted on *///装载点所在的父文件系统
struct dentry *mnt_mountpoint; /* dentry of mountpoint *///装载点在父文件系统中的dentry
struct dentry *mnt_root; /* root of the mounted tree *///当前文件系统根目录的dentry
struct super_block *mnt_sb; /* pointer to superblock *///指向超级块的指针
struct list_head mnt_mounts; /* list of children, anchored here *///子文件系统链表
struct list_head mnt_child; /* and going through their mnt_child *///用于连接到父文件系统中的mnt_mounts
int mnt_flags;
/* 4 bytes hole on 64bits arches */
char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list; //一个命名空间的所有装载的文件系统都保存在namespace->list链表中。使用vfsmount的mnt_list成员作为链表元素
struct list_head mnt_expire; /* link in fs-specific expiry list */ /* 链表元素,用于特定于文件系统的到期链表中 */
struct list_head mnt_share; /* circular list of shared mounts */ /* 链表元素,用于共享装载的循环链表 */
struct list_head mnt_slave_list;/* list of slave mounts */ /* 从属装载的链表 */
struct list_head mnt_slave; /* slave list entry */ /* 链表元素,用于从属装载的链表 */
struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace *///所属的命名空间
/*
* We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
* to let these frequently modified fields in a separate cache line
* (so that reads of mnt_flags wont ping-pong on SMP machines)
*/
atomic_t mnt_count; //mnt_count实现了一个使用计数器。每当一个vfsmount实例不再需要时,都必须用mntput将计数器减1。mntget与mntput相对,在获取vfsmount实例使用时,必须调用mntget。
int mnt_expiry_mark; /* true if marked for expiry */ /* 如果标记为到期,则其值为true */
int mnt_pinned;
};
超级块的定义非常冗长,因此我们给出一个简化的版本
<fs.h>
struct super_block {
struct list_head s_list; //将系统中所有的超级块聚集到一个链表中。该链表的表头是全局变量super_blocks
dev_t s_dev; /* 搜索索引,不是kdev_t */
unsigned long s_blocksize; //指定文件系统的块长度(单位字节)
unsigned char s_blocksize_bits; //指定文件系统的块长度(2^s_blocksize_bits字节)
unsigned char s_dirt; //如果以任何方式改变了超级块,需要向磁盘回写,都会将s_dirt设置为1.否则,其值为0.
unsigned long long s_maxbytes; /* 最大的文件长度 */
struct file_system_type *s_type; //指向文件系统
struct super_operations *s_op;
unsigned long s_flags;
unsigned long s_magic;
struct dentry *s_root; //将超级块与全局根目录的dentry项关联起来(为NULL,则该文件系统是一个伪文件系统,只在内核内部可见)
struct xattr_handler **s_xattr; //该结构包含了一些用于处理扩展属性的函数指针
struct list_head s_inodes; /* 所有inode的链表 */
struct list_head s_dirty; /* 脏inode的链表 */
struct list_head s_io; /* 等待回写 */
struct list_head s_more_io; /* 等待回写,另一个链表 */
struct list_head s_files; //包含了一系列file结构,列出了该超级块表示的文件系统上所有打开的文件
struct block_device *s_bdev; // s_dev和s_bdev指定了底层文件系统的数据所在的块设备
struct list_head s_instances; //各个超级块都连接到另一个链表中,表示同一类型文件系统的所有超级块实例。表头是file_system_type结构的fs_supers成员。
char s_id[32]; /* 有意义的名字 */
void *s_fs_info; /* 文件系统私有信息 */
/* 创建/修改/访问时间的粒度,单位为ns(纳秒)。 粒度不能大于1秒 */
u32 s_time_gran;
};
<fs.h>
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*read_inode) (struct inode *);//读取inode数据,参数为inode的编号,通过参数传递
void (*dirty_inode) (struct inode *);//将传递的inode结构标记为脏的,因为其数据已经修改
int (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);//将inode使用计数器减1
void (*drop_inode) (struct inode *);
void (*delete_inode) (struct inode *);//将inode从内存和底层存储介质删除
void (*put_super) (struct super_block *);//将超级块的私有信息从内存移除,这发生在文件系统卸载、该数据不再需要时
void (*write_super) (struct super_block *);//将超级块写入存储介质
int (*sync_fs)(struct super_block *sb, int wait);//将文件系统数据与底层块设备上的数据同步。
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);//给出有关文件系统的统计信息
int (*remount_fs) (struct super_block *, int *, char *);//重新装载一个已经装载的文件系统
void (*clear_inode) (struct inode *);//当某个inode不再使用时,由VFS在内部调用clear_inode。它释放仍然包含数据的所有相关的内存页面
void (*umount_begin) (struct vfsmount *, int);//仅用于网络文件系统(NFS、CIFS和9fs)和用户空间文件系统(FUSE)。
int (*show_options)(struct seq_file *, struct vfsmount *);//用于proc文件系统,用于显示文件系统装载的选项
int (*show_stats)(struct seq_file *, struct vfsmount *);//用于proc文件系统,提供了文件系统的统计信息。
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
#endif
};
mount系统调用
mount->sys_mount->do_new_mount
static int do_new_mount(struct nameidata *nd, char *type, int flags,
int mnt_flags, char *name, void *data)
{
struct vfsmount *mnt;
if (!type || !memchr(type, 0, PAGE_SIZE))
return -EINVAL;
/* we need capabilities... */
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
mnt = do_kern_mount(type, flags, name, data);//使用get_fs_type找到匹配的file_system_type实例,然后分配并初始化vfsmount结构,
if (IS_ERR(mnt))
return PTR_ERR(mnt);
return do_add_mount(mnt, nd, mnt_flags, NULL);//处理一些必需的锁定操作,并确保一个文件系统不会重复装载到同一位置。主要工作委托给graft_tree。
}
nameidata结构用于将一个vfsmount实例和一个dentry实例聚集起来。在这里,该结构保存了装载点的dentry实例和该目录此前(即新的装载操作执行之前)所在文件系统的vfsmount实例。
graft_tree -> attach_recursive_mount:将新装载的文件系统添加到父文件系统的命名空间。
attach_recursive_mount -> mnt_set_mountpoint:确保新的vfsmount实例的mnt_parent成员指向父文件系统的vfsmount实例,而mnt_mountpoint成员指向装载点所在文件系统中的dentry实例。
attach_recursive_mount -> commit_tree:将新的vfsmount实例添加到全局散列表( list_add_tail(&mnt->mnt_hash, mount_hashtable +hash(parent,mnt->mnt_mountpoint)); )以及父文件系统vfsmount实例中的子文件系统链表.(hash(父挂在vfsmount,父dentry))
mount系统调用分析:https://www.cnblogs.com/cslunatic/p/3683117.html
umount实现:使用保存在mnt_mountpoint和mnt_parent中的数据,将环境恢复到所述文件系统装载之前的原始状态。
伪文件系统
伪文件系统是不能装载的文件系统,因而不可能从用户层直接看到,内核可以用kern_mount或kern_mount_data装载一个伪文件系统。
文件操作
查找inode
nameidata结构用来向查找函数传递参数,并保存查找结果。
<namei.h>
struct nameidata {
struct dentry *dentry;// 查找完成之后,dentry和mnt包含了找到的文件系统项的数据
struct vfsmount *mnt;
struct qstr last;//包含了需要查找的名称
unsigned int flags;
int last_type;
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
/* Intent data */
union {
struct open_intent open;
} intent;
};
内核使用path_lookup函数查找路径或文件名
<fs/namei.c>
int fastcall path_lookup(const char *name, unsigned int flags,
struct nameidata *nd);//name:所需的名称,flags标志,nd:临时结果的“暂存器”
path_lookup->do_path_lookup
<fs/namei.c>
static int fastcall do_path_lookup(int dfd, const char *name,
unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
struct fs_struct *fs = current->fs;
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
nd->depth = 0;
if (*name=='/') {//使用当前根目录的dentry和vfsmount实例作为起点
read_lock(&fs->lock);
if (fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
nd->mnt = mntget(fs->altrootmnt);
nd->dentry = dget(fs->altroot);
read_unlock(&fs->lock);
if (__emul_lookup_dentry(name,nd))
goto out; /* found in altroot */
read_lock(&fs->lock);
}
nd->mnt = mntget(fs->rootmnt);
nd->dentry = dget(fs->root);
read_unlock(&fs->lock);
} else if (dfd == AT_FDCWD) {//或者使用当前工作目录作为起点
read_lock(&fs->lock);
nd->mnt = mntget(fs->pwdmnt);
nd->dentry = dget(fs->pwd);
read_unlock(&fs->lock);
} else {
struct dentry *dentry;
file = fget_light(dfd, &fput_needed);
retval = -EBADF;
if (!file)
goto out_fail;
dentry = file->f_path.dentry;
retval = -ENOTDIR;
if (!S_ISDIR(dentry->d_inode->i_mode))
goto fput_fail;
retval = file_permission(file, MAY_EXEC);
if (retval)
goto fput_fail;
nd->mnt = mntget(file->f_path.mnt);
nd->dentry = dget(dentry);
fput_light(file, fput_needed);
}
retval = path_walk(name, nd);
out:
if (unlikely(!retval && !audit_dummy_context() && nd->dentry &&
nd->dentry->d_inode))
audit_inode(name, nd->dentry);
out_fail:
return retval;
fput_fail:
fput_light(file, fput_needed);
goto out_fail;
}
path_walk --> link_path_walk --> __link_path_walk
<fs/namei.c>
static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
{
struct path next;
struct inode *inode;
int err;
unsigned int lookup_flags = nd->flags;
while (*name=='/')
name++;
if (!*name)
goto return_reval;
inode = nd->dentry->d_inode;
if (nd->depth)
lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
/* At this point we know we have a real path component. */
for(;;) {
unsigned long hash;
struct qstr this;
unsigned int c;
nd->flags |= LOOKUP_CONTINUE;
err = exec_permission_lite(inode, nd);//权限检查
if (err == -EAGAIN)
err = vfs_permission(nd, MAY_EXEC);//调用inode_operations的permission方法进行权限检查
if (err)
break;//权限错误
this.name = name;
c = *(const unsigned char *)name;
hash = init_name_hash();
do {
name++;
hash = partial_name_hash(c, hash);
c = *(const unsigned char *)name;
} while (c && (c != '/'));
this.len = name - (const char *) this.name;//提取路径中分量
this.hash = end_name_hash(hash);//计算散列值
/* remove trailing slashes? */
if (!c)//
goto last_component;//当前分量为最后一个分量
while (*++name == '/');//为下一次循环做准备,跳过‘/’
if (!*name)
goto last_with_slashes;
/*
* "." and ".." are special - ".." especially so because it has
* to be able to know about the current root directory and
* parent relationships.
*/
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2: //两个点的情况,需要回退
if (this.name[1] != '.')
break;
follow_dotdot(nd);
/*follow_dotdot函数。当查找操作处理进程的根目录时,..是没有效果的,因为无法切换到根目录的父目录。否则,有两个可用的选项。如果当前目录不是一个装载点的根目录,则将当前dentry对象的d_parent成员用作新的目录,因为它总是表示父目录。但如果当前目录是一个已装载文件系统的根目录,保存在mnt_mountpoint和mnt_parent中的信息用于定义新的dentry和vfsmount对象。follow_mount和lookup_mnt用于取得所需的信息(follow_mount,找到最后的挂载点)*/
inode = nd->dentry->d_inode;
/* fallthrough */
case 1://一个点(.),表示当前目录,内核将直接跳过查找循环的下一个周期,因为在目录层次结构中的位置没有改变
continue;
}
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
err = nd->dentry->d_op->d_hash(nd->dentry, &this);
if (err < 0)
break;
}
/* This does the actual lookups.. */
err = do_lookup(nd, &this, &next);//查找分量对应的dentry实例
if (err)
break;
err = -ENOENT;
inode = next.dentry->d_inode;
if (!inode)
goto out_dput;
err = -ENOTDIR;
if (!inode->i_op)
goto out_dput;
if (inode->i_op->follow_link) {//下面进行符号连接的处理
err = do_follow_link(&next, nd);
if (err)
goto return_err;
err = -ENOENT;
inode = nd->dentry->d_inode;
if (!inode)
break;
err = -ENOTDIR;
if (!inode->i_op)
break;
} else
path_to_nameidata(&next, nd);
err = -ENOTDIR;
if (!inode->i_op->lookup)
break;
continue;
/* here ends the main loop */
last_with_slashes:
lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
/* Clear LOOKUP_CONTINUE iff it was previously unset */
nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
if (lookup_flags & LOOKUP_PARENT)
goto lookup_parent;
if (this.name[0] == '.') switch (this.len) {
default:
break;
case 2:
if (this.name[1] != '.')
break;
follow_dotdot(nd);
inode = nd->dentry->d_inode;
/* fallthrough */
case 1:
goto return_reval;
}
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
err = nd->dentry->d_op->d_hash(nd->dentry, &this);
if (err < 0)
break;
}
err = do_lookup(nd, &this, &next);
if (err)
break;
inode = next.dentry->d_inode;
if ((lookup_flags & LOOKUP_FOLLOW)
&& inode && inode->i_op && inode->i_op->follow_link) {
err = do_follow_link(&next, nd);
if (err)
goto return_err;
inode = nd->dentry->d_inode;
} else
path_to_nameidata(&next, nd);
err = -ENOENT;
if (!inode)
break;
if (lookup_flags & LOOKUP_DIRECTORY) {
err = -ENOTDIR;
if (!inode->i_op || !inode->i_op->lookup)
break;
}
goto return_base;
lookup_parent:
nd->last = this;
nd->last_type = LAST_NORM;
if (this.name[0] != '.')
goto return_base;
if (this.len == 1)
nd->last_type = LAST_DOT;
else if (this.len == 2 && this.name[1] == '.')
nd->last_type = LAST_DOTDOT;
else
goto return_base;
return_reval:
/*
* We bypassed the ordinary revalidation routines.
* We may need to check the cached dentry for staleness.
*/
if (nd->dentry && nd->dentry->d_sb &&
(nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
err = -ESTALE;
/* Note: we do not d_invalidate() */
if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))
break;
}
return_base:
return 0;
out_dput:
dput_path(&next, nd);
break;
}
path_release(nd);
return_err:
return err;
}
do_lookup的实现
<fs/namei.c>
static int do_lookup(struct nameidata *nd, struct qstr *name,
struct path *path)
{
struct vfsmount *mnt = nd->mnt;
struct dentry *dentry = __d_lookup(nd->dentry, name);//试图在dentry缓存中查找inode
if (!dentry)
goto need_lookup;
if (dentry->d_op && dentry->d_op->d_revalidate)
goto need_revalidate;
done:
path->mnt = mnt;
path->dentry = dentry;
__follow_mount(path);//确保已装载文件系统的根目录用作装载点(可能有几个文件系统相继装载到前一个文件系统中,,除了后一个文件系统,所有其他文件系统都被相邻的后一个文件系统隐藏)
return 0;
need_lookup:
dentry = real_lookup(nd->dentry, name, nd);//缓存无效,必须从底层文件系统中发起一个查找操作
if (IS_ERR(dentry))
goto fail;
goto done;
need_revalidate:
dentry = do_revalidate(dentry, nd);
if (!dentry)
goto need_lookup;
if (IS_ERR(dentry))
goto fail;
goto done;
fail:
return PTR_ERR(dentry);
}
do_follow_link的实现:
在内核跟踪符号链接时,它必须要注意用户可能构造出的环状结构,如:a->b,b->c,c->a。如果内核不采取适当的防护措施,这可能被利用,导致系统变得不可用。
打开文件
sys_open->do_sys_open->do_filp_open
do_filp_open->open_namei:调用path_lookup函数查找inode并执行几个额外的检查。
do_filp_open->nameidata_to_filp:初始化预读结构,将新创建的file实例放置到超级块的s_files链表上,并调用底层文件系统的file_operations结构的open函数
标准函数
通用读取例程
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
struct iovec iov = { .iov_base = 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_read(&kiocb, &iov, 1, kiocb.ki_pos);//通常为generic_file_aio_read(异步)
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;
}
<mm/filemap.c>
ssize_t
generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct file *filp = iocb->ki_filp;
ssize_t retval;
unsigned long seg;
size_t count;
loff_t *ppos = &iocb->ki_pos;
count = 0;
retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE);//确认读请求包含的参数是否有效
if (retval)
return retval;
/* coalesce the iovecs and go direct-to-BIO for O_DIRECT */
if (filp->f_flags & O_DIRECT) {//直接读取,不使用页缓存
loff_t size;
struct address_space *mapping;
struct inode *inode;
mapping = filp->f_mapping;
inode = mapping->host;
retval = 0;
if (!count)
goto out; /* skip atime */
size = i_size_read(inode);
if (pos < size) {
retval = generic_file_direct_IO(READ, iocb,
iov, pos, nr_segs);
if (retval > 0)
*ppos = pos + retval;
}
if (likely(retval != 0)) {
file_accessed(filp);
goto out;
}
}
retval = 0;
if (count) {
for (seg = 0; seg < nr_segs; seg++) {
read_descriptor_t desc;
desc.written = 0;
desc.arg.buf = iov[seg].iov_base;
desc.count = iov[seg].iov_len;
if (desc.count == 0)
continue;
desc.error = 0;
do_generic_file_read(filp,ppos,&desc,file_read_actor);//该函数将对文件的读操作转换为对映射的读操作
retval += desc.written;
if (desc.error) {
retval = retval ?: desc.error;
break;
}
if (desc.count > 0)
break;
}
}
out:
return retval;
}
do_generic_file_read->do_generic_mapping_read
void do_generic_mapping_read(struct address_space *mapping,
struct file_ra_state *ra,
struct file *filp,
loff_t *ppos,
read_descriptor_t *desc,
read_actor_t actor)
{
struct inode *inode = mapping->host;
pgoff_t index;
pgoff_t last_index;
pgoff_t prev_index;
unsigned long offset; /* offset into pagecache page */
unsigned int prev_offset;
int error;
index = *ppos >> PAGE_CACHE_SHIFT;
prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1);
last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT;
offset = *ppos & ~PAGE_CACHE_MASK;
for (;;) {
struct page *page;
pgoff_t end_index;
loff_t isize;
unsigned long nr, ret;
cond_resched();
find_page:
page = find_get_page(mapping, index);
if (!page) {//页没有在页缓存中
page_cache_sync_readahead(mapping,
ra, filp,
index, last_index - index);
page = find_get_page(mapping, index);//发出一个同步预读请求
if (unlikely(page == NULL))
goto no_cached_page;
}
if (PageReadahead(page)) {//检查是否需要进行异步预读操作
page_cache_async_readahead(mapping,
ra, filp, page,
index, last_index - index);
}
if (!PageUptodate(page))//检查页缓存中的数据是否是最新的
goto page_not_up_to_date;
page_ok:
/*
* i_size must be checked after we know the page is Uptodate.
*
* Checking i_size after the check allows us to calculate
* the correct value for "nr", which means the zero-filled
* part of the page is not copied back to userspace (unless
* another truncate extends the file - this is desired though).
*/
isize = i_size_read(inode);
end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
if (unlikely(!isize || index > end_index)) {
page_cache_release(page);
goto out;
}
/* nr is the maximum number of bytes to copy from this page */
nr = PAGE_CACHE_SIZE;
if (index == end_index) {
nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
if (nr <= offset) {
page_cache_release(page);
goto out;
}
}
nr = nr - offset;
/* If users can be writing to this page using arbitrary
* virtual addresses, take care about potential aliasing
* before reading the page on the kernel side.
*/
if (mapping_writably_mapped(mapping))
flush_dcache_page(page);
/*
* When a sequential read accesses a page several times,
* only mark it as accessed the first time.
*/
if (prev_index != index || offset != prev_offset)
mark_page_accessed(page);
prev_index = index;
/*
* Ok, we have the page, and it's up-to-date, so
* now we can copy it to user space...
*
* The actor routine returns how many bytes were actually used..
* NOTE! This may not be the same as how much of a user buffer
* we filled up (we may be padding etc), so we can only update
* "pos" here (the actor routine has to update the user buffer
* pointers and the remaining count).
*/
ret = actor(desc, page, offset, nr);
offset += ret;
index += offset >> PAGE_CACHE_SHIFT;
offset &= ~PAGE_CACHE_MASK;
prev_offset = offset;
page_cache_release(page);
if (ret == nr && desc->count)
continue;
goto out;
page_not_up_to_date:
/* Get exclusive access to the page ... */
lock_page(page);
/* Did it get truncated before we got the lock? */
if (!page->mapping) {
unlock_page(page);
page_cache_release(page);
continue;
}
/* Did somebody else fill it already? */
if (PageUptodate(page)) {
unlock_page(page);
goto page_ok;
}
readpage:
/* Start the actual read. The read will unlock the page. */
error = mapping->a_ops->readpage(filp, page);
if (unlikely(error)) {
if (error == AOP_TRUNCATED_PAGE) {
page_cache_release(page);
goto find_page;
}
goto readpage_error;
}
if (!PageUptodate(page)) {
lock_page(page);
if (!PageUptodate(page)) {
if (page->mapping == NULL) {
/*
* invalidate_inode_pages got it
*/
unlock_page(page);
page_cache_release(page);
goto find_page;
}
unlock_page(page);
error = -EIO;
shrink_readahead_size_eio(filp, ra);
goto readpage_error;
}
unlock_page(page);
}
goto page_ok;
readpage_error:
/* UHHUH! A synchronous read error occurred. Report it */
desc->error = error;
page_cache_release(page);
goto out;
no_cached_page:
/*
* Ok, it wasn't cached, so we need to create a new
* page..
*/
page = page_cache_alloc_cold(mapping);
if (!page) {
desc->error = -ENOMEM;
goto out;
}
error = add_to_page_cache_lru(page, mapping,
index, GFP_KERNEL);
if (error) {
page_cache_release(page);
if (error == -EEXIST)
goto find_page;
desc->error = error;
goto out;
}
goto readpage;
}
out:
ra->prev_pos = prev_index;
ra->prev_pos <<= PAGE_CACHE_SHIFT;
ra->prev_pos |= prev_offset;
*ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset;
if (filp)
file_accessed(filp);
}
Linux内核入门到放弃-虚拟文件系统-《深入Linux内核架构》笔记的更多相关文章
- Linux内核入门到放弃-网络-《深入Linux内核架构》笔记
网络命名空间 struct net { atomic_t count; /* To decided when the network * namespace should be freed. */ a ...
- Linux内核入门到放弃-模块-《深入Linux内核架构》笔记
使用模块 依赖关系 modutils标准工具集中的depmod工具可用于计算系统的各个模块之间的依赖关系.每次系统启动时或新模块安装后,通常都会运行该程序.找到的依赖关系保存在一个列表中.默认情况下, ...
- Linux从入门到放弃、零基础入门Linux(第三篇):在虚拟机vmware中安装linux(二)超详细手把手教你安装centos6分步图解
一.继续在vmware中安装centos6.9 本次安装是进行最小化安装,即没有图形化界面的安装,如果是新手,建议安装带图形化界面的centos, 具体参考Linux从入门到放弃.零基础入门Linux ...
- Linux从入门到放弃、零基础入门Linux(第四篇):在虚拟机vmware中安装centos7.7
如果是新手,建议安装带图形化界面的centos,这里以安装centos7.7的64位为例 一.下载系统镜像 镜像文件下载链接https://wiki.centos.org/Download 阿里云官网 ...
- Linux内核入门到放弃-无持久存储的文件系统-《深入Linux内核架构》笔记
proc文件系统 proc文件系统是一种虚拟的文件系统,其信息不能从块设备读取.只有在读取文件内容时,才动态生成相应的信息. /proc的内容 内存管理 系统进程的特征数据 文件系统 设备驱动程序 系 ...
- (笔记)Linux内核学习(十)之虚拟文件系统概念
虚拟文件系统 虚拟文件系统:内核子系统VFS,VFS是内核中文件系统的抽象层,为用户空间提供文件系统相关接口: 通过虚拟文件系统,程序可以利用标准Linux文件系统调用在不同的文件系统中进行交互和操作 ...
- Linux内核入门到放弃-页缓存和块缓存-《深入Linux内核架构》笔记
内核为块设备提供了两种通用的缓存方案. 页缓存(page cache) 块缓存(buffer cache) 页缓存的结构 在页缓存中搜索一页所花费的时间必须最小化,以确保缓存失效的代价尽可能低廉,因为 ...
- Linux内核入门到放弃-Ext2数据结构-《深入Linux内核架构》笔记
Ext2文件系统 物理结构 结构概观 块组是该文件系统的基本成分,容纳了文件系统的其他结构.每个文件系统都由大量块组组成,在硬盘上相继排布: ----------------------------- ...
- Linux内核入门到放弃-设备驱动程序-《深入Linux内核架构》笔记
I/O体系结构 总线系统 PCI(Peripheral Component Interconnect) ISA(Industrial Standard Architecture) SBus IEEE1 ...
随机推荐
- 二进制安装部署kubernetes集群---超详细教程
本文收录在容器技术学习系列文章总目录 前言:本篇博客是博主踩过无数坑,反复查阅资料,一步步搭建完成后整理的个人心得,分享给大家~~~ 本文所需的安装包,都上传在我的网盘中,需要的可以打赏博主一杯咖啡钱 ...
- 第46章 发现端点(Discovery Endpoint) - Identity Server 4 中文文档(v1.0.0)
发现端点可用于检索有关IdentityServer的元数据 - 它返回发布者名称,密钥材料,支持的范围等信息.有关详细信息,请参阅规范. 发现端点可通过/.well-known/openid-conf ...
- MySQL 笔记整理(5) --深入浅出索引(下)
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 5) --深入浅出索引(下) 这次的笔记从一个简单的查询开始: 建表语句是这样的 mysql> create table T ...
- _C#发送邮箱
public ActionResult lead() { SendEmail("邮箱号", "吃饭么?", "你要吃什么啊"); retur ...
- python之编码与解码
编码 字符串被当作url提交时会被自动进行url编码处理,在python里也有个urllib.urlencode的方法,可以很方便的把字典形式的参数进行url编码.当url地址含有中文或者“/”的时候 ...
- [Go] 使用go语言解决现代编程难题
1.计算机一直在演化,64核,128核等等,但是我们依旧在使用为单核设计的技术编程2.Go语言让分享自己的代码包更容易3.Go语言重新思考传统的面向对象,提供了更高效的复用代码手段4.Go不仅提供高性 ...
- Docker 安装rabbitMQ
Docker 安装rabbitMQ docker pull rabbitmq:3.7.7-management 使用:docker images 查看所有镜像 4.根据下载的镜像创建和启动容器 doc ...
- Linux tail 命令
tail 命令可用于查看文件的内容,有一个常用的参数 -f 常用于查阅正在改变的日志文件. tail -f filename 会把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新,只 ...
- mac 安装protobuf,并编译为java,c++,python
1.下载地址:https://code.google.com/p/protobuf/downloads/list 另外,可以查看这个链接查看中文更多内容:http://www.cnblogs.com/ ...
- QT 启动shell脚本
1.QProcess *p = new QProcess(this); 2.QString str = qApp->applicationDirPath() + "/update.sh ...