day6 文件系统read_write

2018-12-10  本文已影响0人  柯基是只dog

name.c

该文件代码比较长,有700多行,但确实算我们比较熟悉的了,按名操作,在shell里算是基本操作了,让我们来看看具体实现

// permission权限
// 我们很熟悉了,linux文件可以设置读写权限rwx,比如一个正常的文件-r--r-xrwx,
// 就代表所属者可以rwx权限,组员可以rx,其他人只能r。具体实现看代码把
/*
 *  permission()
 *
 * 该函数用于检测一个文件的读/写/执行权限。我不知道是否只需检查euid,还是
 * 需要检查euid 和uid 两者,不过这很容易修改。
 */
//// 检测文件访问许可权限。
// 参数:inode - 文件对应的i 节点;mask - 访问属性屏蔽码。
// 返回:访问许可返回1,否则返回0。
static int permission(struct m_inode * inode,int mask)
{
    int mode = inode->i_mode;// 文件访问属性

/* 特殊情况:即使是超级用户(root)也不能读/写一个已被删除的文件 */
// 如果i 节点有对应的设备,但该i 节点的连接数等于0,则返回。
    if (inode->i_dev && !inode->i_nlinks)
        return 0;
// 否则,如果进程的有效用户id(euid)与i 节点的用户id 相同,则取文件宿主的用户访问权限。
    else if (current->euid==inode->i_uid)
        mode >>= 6;
// 否则,如果进程的有效组id(egid)与i 节点的组id 相同,则取组用户的访问权限。
    else if (current->egid==inode->i_gid)
        mode >>= 3;
// 如果上面所取的的访问权限与屏蔽码相同,或者是超级用户,则返回1,否则返回0。
    if (((mode & mask & 0007) == mask) || suser()) // suser()在linux/kernel.h。
        return 1;
    return 0;
}

#### 2. find_entry&&match
// 根据文件名和长度在文件夹节点里查找是否有该文件夹,首先我们知道文件夹节点的data里存的是一个16字节的结构如下。
// 那么该方法逻辑就是找到该节点,然后从i_zone里读取数据(多级目录底层实现了)
// 值得注意的一点是要判断文件名是".."的情况
// 因为上级目录可能和当前的文件系统不是一个分区。剩下的就很简单了,就是从i_zone对应的数据块读出数据,
// 然后按名字去匹配是否有,还有一点很值得注意,因为代码执行的位置是内核态,栈用的也是内核的栈
// 但是文件名和长度都是保存在用户栈上的。linus写了一个match方法,用的是汇编,对应的寻址是fs:+对应的地址来完成对用户态地址的寻址的。

// 文件目录项结构。
struct dir_entry
{
  unsigned short inode;     // i 节点。
  char name[NAME_LEN];      // 文件名。
};

file_dev.c

该文件是文件类型的读写操作,我们看文件的结构对象,写的时候比较模式,如果是append,则pos为文件节点的size字段(就是指向文件最后),通过pos而且我们知道我们的文件系统的逻辑块等于物理块大小,所以用pos/1024就可以得到在文件节点的偏移号,调用create_block可以得到实际的block,然后调用bread读取该块的数据,而块内的偏移是pos%1024得到(发现是不是和分页特别像!),然后通过复制从用户空间把内容写到高速缓冲区中,设置缓冲区的dirty为1

// 文件结构(用于在文件句柄与i 节点之间建立关系)
struct file
{
  unsigned short f_mode;    // 文件操作模式(RW 位)
  unsigned short f_flags;   // 文件打开和控制的标志。
  unsigned short f_count;   // 对应文件句柄(文件描述符)数。
  struct m_inode *f_inode;  // 指向对应i 节点。
  off_t f_pos;          // 文件位置(读写偏移值)。
};

pipe.c

因为有了文件系统,管道的实现就很好理解了,管道只是伪造了一个管道节点,但实际不存在外存中,但因为有了统一的文件操作方式,上层应用就可以用文件操作的方式读取管道。所以说文件的抽象是linux系统的一种伟大体现。linux规定管道的头指针放在i_zone[0]里,尾指针放在i_zone[1]里。

创建的时候会调用get_free_page申请一页内存并把指针给与i_size字段,还有一点有意思的是,管道只有一页就是4K大小,头尾指针刚开始都是指向i_size也就是申请的页开头,当写入的时候指针往后偏移,读的时候也往后偏移,程序保证不超过4K,当指针超过4K的时候,会PIPE_TAIL(*inode) &= (PAGE_SIZE-1);来让指针从页最后重新跑到开始的地方循环。

// 管道头、管道尾、管道大小、管道空?、管道满?、管道头指针递增。
#define PIPE_HEAD(inode) ((inode).i_zone[0])
#define PIPE_TAIL(inode) ((inode).i_zone[1])
#define PIPE_SIZE(inode) ((PIPE_HEAD(inode)-PIPE_TAIL(inode))&(PAGE_SIZE-1))
#define PIPE_EMPTY(inode) (PIPE_HEAD(inode)==PIPE_TAIL(inode))
#define PIPE_FULL(inode) (PIPE_SIZE(inode)==(PAGE_SIZE-1))
//#define INC_PIPE(head) \
//__asm__( "incl %0\n\tandl $4095,%0":: "m" (head))
#define INC_PIPE(head) _INC_PIPE(&(head))
extern _inline void _INC_PIPE(unsigned long *head) {
    _asm mov ebx,head
    _asm inc dword ptr [ebx]
    _asm and dword ptr [ebx],4095
}

read_write.c

该文件就是其他几个底层文件的上层模块,lseek,read,write都会根据设备种类调用对应的方法去执行。其中lseek是改变指针位置,但不支持管道设备,其他就根据参数判断偏移是从文件头或者当前指针或者文件结尾的位置。

read和write就是直接对应系统调用的读调用,通过判断设备类型调用底层真正的读写方法,然后写入高速缓冲区内,最后通过远指正从内核的高速缓冲区写入用户的内存中。

上一篇下一篇

猜你喜欢

热点阅读