系列文章目录

Linux 0.11启动过程分析(一)
Linux 0.11 fork 函数(二)
Linux0.11 缺页处理(三)
Linux0.11 根文件系统挂载(四)
Linux0.11 文件打开open函数(五)
Linux0.11 execve函数(六)
Linux0.11 80X86知识(七)
Linux0.11 内核体系结构(八)
Linux0.11 系统调用进程创建与执行(九)
Linux0.11 进程切换(十)
Linux0.11 管道(十一)
Linux0.11 信号(十二)


文章目录

  • 系列文章目录
  • 前言
  • 一、由来
    • 1、open函数
  • 二、sys_open 函数
    • 1、open_namei 函数
      • dir_namei 函数
        • get_dir 函数
          • find_entry 函数

前言

  文件系统根目录下的所有文件名信息则保持在指定 i 节点(即1号节点)的数据块中。每个目录项只包括一个长度为 14 字节的文件名字字符串和该文件名对应的 2 字节的 i 节点号。有关文件的其它信息则被保存在该 i 节点号指定的 i 节点结构中,该结构中主要包括文件访问属性、宿主、长度、访问保存时间以及所在磁盘块等信息。

#define NAME_LEN 14            // 名字长度值
#define ROOT_INO 1             // 根 i 节点struct dir_entry {unsigned short inode;      // i 节点号(0.11版本可理解为存储序号)char name[NAME_LEN];       // 文件名
};

  在打开一个文件时,文件系统会根据给定的文件名找到其 i 节点号,从而找到文件所在的磁盘块位置。例如对于要查找文件名 /usr/bin/vi 的 i 节点号,文件系统首先会从具有固定 i 节点号(1)的根目录开始操作,即从 i 节点号 1 的数据块中查找到名称为 usr 的目录项,从而得到文件 /usr 的 i 节点号。根据该 i 节点号文件系统可以顺利地取得目录 /usr,并在其中可以查找到文件名 bin 的目录项。这样也就知道了 /usr/bin 的 i 节点号,因而我们可以知道目录 /usr/bin 的目录所在位置,并在该目录中查找到 vi 文件的目录项。最终我们获得了文件路径名 /usr/bin/vi 的 i 节点号,从而可以从磁盘上得到该 i 节点号的 i 节点结构信息。


一、由来

在文件 init/main.c 的 init 函数中调用了 open 函数:

void init(void) {// ...  (void)open("/dev/tty0", O_RDWR, 0);(void)dup(0);(void)dup(0);// ...
}

接下来我们分析下其调用过程。

1、open函数

// lib/open.c
int open(const char * filename, int flag, ...)
{register int res;va_list arg;va_start(arg,flag);__asm__("int $0x80":"=a" (res):"0" (__NR_open),"b" (filename),"c" (flag),"d" (va_arg(arg,int)));if (res>=0)return res;errno = -res;return -1;
}

从上面可知,这是一个系统调用,其中断响应为 kernel/system_call.s 文件中 system_call 函数,最终调用 sys_open 函数(具体可参考 Linux 0.11 fork 函数(二))。

二、sys_open 函数

函数位于文件 fs/open.c 中。

// fs/open.c
int sys_open(const char * filename,int flag,int mode)
{struct m_inode * inode;struct file * f;int i,fd;mode &= 0777 & ~current->umask;for(fd=0 ; fd<NR_OPEN ; fd++)if (!current->filp[fd])break;if (fd>=NR_OPEN)return -EINVAL;current->close_on_exec &= ~(1<<fd);f=0+file_table;for (i=0 ; i<NR_FILE ; i++,f++)if (!f->f_count) break;if (i>=NR_FILE)return -EINVAL;(current->filp[fd]=f)->f_count++;if ((i=open_namei(filename,flag,mode,&inode))<0) {current->filp[fd]=NULL;f->f_count=0;return i;}
/* ttys are somewhat special (ttyxx major==4, tty major==5) */if (S_ISCHR(inode->i_mode)) {if (MAJOR(inode->i_zone[0])==4) {if (current->leader && current->tty<0) {current->tty = MINOR(inode->i_zone[0]);tty_table[current->tty].pgrp = current->pgrp;}} else if (MAJOR(inode->i_zone[0])==5)if (current->tty<0) {iput(inode);current->filp[fd]=NULL;f->f_count=0;return -EPERM;}}
/* Likewise with block-devices: check for floppy_change */if (S_ISBLK(inode->i_mode))check_disk_change(inode->i_zone[0]);f->f_mode = inode->i_mode;f->f_flags = flag;f->f_count = 1;f->f_inode = inode;f->f_pos = 0;return (fd);
}

1、open_namei 函数

函数位于文件 fs/namei.c 中。

// fs/namei.c
// 文件打开函数
// pathname是文件名,flag是打开文件标志,可为:O_RDONLY(只读)、O_WRONLY(只写)或
// O_RDWR(读写),以及O_CREAT(创建)、O_EXCL(被创建文件必须不存在)、O_APPEND(在文件尾添加数据)
// 等其他一些标志的组合。
// 成功返回0,否则返回出错码,res_node返回对应文件路径名的i节点指针
int open_namei(const char * pathname, int flag, int mode,struct m_inode ** res_inode)
{const char * basename;int inr,dev,namelen;struct m_inode * dir, *inode;struct buffer_head * bh;struct dir_entry * de;if ((flag & O_TRUNC) && !(flag & O_ACCMODE))flag |= O_WRONLY;mode &= 0777 & ~current->umask;mode |= I_REGULAR;if (!(dir = dir_namei(pathname,&namelen,&basename)))return -ENOENT;if (!namelen) {           /* special case: '/usr/' etc */if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {*res_inode=dir;return 0;}iput(dir);return -EISDIR;}bh = find_entry(&dir,basename,namelen,&de);if (!bh) {if (!(flag & O_CREAT)) {iput(dir);return -ENOENT;}if (!permission(dir,MAY_WRITE)) {iput(dir);return -EACCES;}inode = new_inode(dir->i_dev);if (!inode) {iput(dir);return -ENOSPC;}inode->i_uid = current->euid;inode->i_mode = mode;inode->i_dirt = 1;bh = add_entry(dir,basename,namelen,&de);if (!bh) {inode->i_nlinks--;iput(inode);iput(dir);return -ENOSPC;}de->inode = inode->i_num;bh->b_dirt = 1;brelse(bh);iput(dir);*res_inode = inode;return 0;}inr = de->inode;dev = dir->i_dev;brelse(bh);iput(dir);if (flag & O_EXCL)return -EEXIST;if (!(inode=iget(dev,inr)))return -EACCES;if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||!permission(inode,ACC_MODE(flag))) {iput(inode);return -EPERM;}inode->i_atime = CURRENT_TIME;if (flag & O_TRUNC)truncate(inode);*res_inode = inode;return 0;
}

dir_namei 函数

函数位于文件 fs/namei.c 中。
该函数返回指定目录名的 i 节点指针,以及在最顶层目录的名称。

static struct m_inode * dir_namei(const char * pathname,int * namelen, const char ** name)
{char c;const char * basename;struct m_inode * dir;if (!(dir = get_dir(pathname)))return NULL;basename = pathname;while ((c=get_fs_byte(pathname++)))if (c=='/')basename=pathname;*namelen = pathname-basename-1;*name = basename;return dir;
}

get_dir 函数

函数位于文件 fs/namei.c 中。
该函数根据给出的路径名进行搜索,直到达到最顶端的目录。

static struct m_inode * get_dir(const char * pathname)
{char c;const char * thisname;struct m_inode * inode;struct buffer_head * bh;int namelen,inr,idev;struct dir_entry * de;// 搜索操作会从当前进程任务结构中设置的根(或伪根)i 节点或当前工作目录 i 节点开始。// 因此首先需要判断进程的根 i 节点指针和当前工作目录 i 节点指针是否有效。如果当前进程// 没有设定根 i 节点,或者该进程根 i 节点指向是一个空闲i节点(引用为0),则系统出错停机。// 如果进程的当前工作目录i节点指针为空,或者当前工作目录 i 节点指向是一个空闲i节点,// 这也是系统有问题,停机。if (!current->root || !current->root->i_count)panic("No root inode");if (!current->pwd || !current->pwd->i_count)panic("No cwd inode");// 如果用户指定的路径名的第1个字符是'/',则说明路径是绝对路径名。则从根i节点开始操作。// 否则表示给定的是相对路径名。应从进程的当前工作目录开始操作。则取进程当前工作目录的// i节点。if ((c=get_fs_byte(pathname))=='/') {inode = current->root;pathname++;} else if (c)inode = current->pwd;elsereturn NULL;  /* empty name is bad */// 然后针对路径名中的各个目录名部分和文件名进行循环处理。首先把得到的i节点引用计数增1,// 表示我们正在使用。在循环处理过程中,我们先要对当前正在处理的目录名部分(或文件名)的i// 节点进行有效性判断,并且把变量thisname指向当前正在处理的目录名部分(或文件名)。// 如果该i节点不是目录类型的i节点,或者没有可进入该目录的访问许可,则放回该i节点,并返回// NULL退出。当然,刚进入循环时,当前的i节点就是进程根i节点或者是当前工作目录的i节点。// 注意!如果路径名中最后一个名称也是一个目录名,但其后面没有加上'/'字符,则函数不会// 返回该最后目录的i节点!例如:对于路径名/usr/src/linux,该函数将只返回src目录名的i节点。inode->i_count++;while (1) {thisname = pathname;if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {iput(inode);return NULL;}// 每次循环我们处理路径名中一个目录名(或文件名)部分。因此在每次循环中我们都要从路径// 名字符串中分离出一个目录名(或文件名)。方法是从当前路径名指针pathname开始处搜索// 检测字符,直到字符是一个结尾符(NULL)或者是一个'/'字符。此时变量namelen正好是当前// 处理目录名部分的长度,而变量thisname正指向该目录名部分的开始处。此时如果字符是结尾符// NULL,则表明已经搜索到路径名末尾,并已到达最后指定目录名或文件名,则返回该i节点指针退出for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)/* nothing */ ;if (!c)return inode;// 在得到当前目录名部分(或文件名)后,我们调用查找目录项函数find_entry在当前处理的目录中// 寻找指定名称的目录项。如果没找到,则返回NULL退出。否则在找到的目录项中取出其i节点号inr// 和设备号idev,然后取节点号inr的i节点inode,并以该目录项为当前目录继续循环处理路径名中的// 下一目录名部分(或文件名)if (!(bh = find_entry(&inode,thisname,namelen,&de))) {iput(inode);return NULL;}inr = de->inode;idev = inode->i_dev;brelse(bh);iput(inode);if (!(inode = iget(idev,inr)))return NULL;}
}
find_entry 函数
/* 查找指定目录和文件名的目录项。* dir 指定目录i节点的指针,name 文件名,namelen文件名长度。* 返回:成功则返回函数告诉缓冲区指针,并在*res_dir处返回的目录项结构指针。失败则返回空指针。
*/
static struct buffer_head * find_entry(struct m_inode ** dir,const char * name, int namelen, struct dir_entry ** res_dir)
{//...
}

Linux0.11 文件打开open函数(五)相关推荐

  1. linux0.11文件分析

    在kernel包中有几个重要的文件夹和文件,他们各司其职,处理着有关内核的一些功能操作.其中文件夹有三个:blk_drv(块设备驱动),chr_drv(字符设备驱动),math(数学协处理器)  文件 ...

  2. linux睡眠当前进程,linux-0.11中进程睡眠函数sleep_on()解析

    sleep_on()用于进程睡眠. 其原型为 void sleep_on(struct task_struct **p); 比如某个资源是互斥的,当资源被某一个进程占用时,其他进程便无法访问此资源. ...

  3. Linux0.11 execve函数(六)

    系列文章目录 Linux 0.11启动过程分析(一) Linux 0.11 fork 函数(二) Linux0.11 缺页处理(三) Linux0.11 根文件系统挂载(四) Linux0.11 文件 ...

  4. 编译linux0.11内核

    编译linux0.11内核 一.实验环境 二.下载文件 三.配置Linux0.11所需环境 四.编译内核 五.运行linux0.11 六.说明 1.setup.sh脚本里进行了什么操作? 2.最后弹出 ...

  5. C语言文件打开方式及说明

    ANSI C规定文件打开用函数fopen,关闭为fclose. 1.调用方式通常为: FILE *fp; fp=fopen(文件名, 打开方式); 2.参数说明: 文件名: 形如"myfil ...

  6. main函数解析(一)——Linux-0.11 学习笔记(五)

    main()函数解析(一)--Linux-0.11 学习笔记(五) 经过了前面的各种铺垫,终于来到了main函数.这篇博客的任务是把init/main.c讲清楚.由于牵扯到很多的函数调用,要想一次就说 ...

  7. main 函数解析(二)—— Linux-0.11 学习笔记(六)

    main函数解析(二)--Linux-0.11 学习笔记(六) 4.6 blk_dev_init函数 void blk_dev_init(void) {int i;for (i=0 ; i<NR ...

  8. Linux0.11 创建进程的过程分析--fork函数的使用

    /* * linux/kernel/fork.c * * (C) 1991 Linus Torvalds */ /* 注意:signal.c和fork.c文件的编译选项内不能有vc变量优化选项/Og, ...

  9. main() 函数解析(一)——Linux-0.11 剖析笔记(六)

    文章目录 1. 宏定义`_syscall0` 2. `setup.s`读取的参数 3. 读取CMOS实时时钟信息 3.1 `outb_p(value,port)` 3.2 `inb_p(port)` ...

最新文章

  1. 关于端口映射的一个命令
  2. 解读Java 8 中为并发而生的 ConcurrentHashMap
  3. 通过命令行使用 JAX-WS调用webservice
  4. 使用ajax实现无刷新邮箱验证码,AJAX和WebService实现邮箱验证(无刷新)
  5. 1.1.1 从简单的数据类型开始
  6. html5的网络书店图书网站代码_【技能提升】10个编写HTML5的实用小技巧
  7. Collecting Bugs POJ - 2096(基础概率dp+期望模板)
  8. 将PostgreSQL PL / Java安装为PostgreSQL扩展
  9. 现代程序设计 作业4
  10. Redis夺命连环11问
  11. NAT (PAT)地址转换技术(讲解+配置)
  12. ssis 映射列 使用变量_SSIS中的动态列映射:SqlBulkCopy类与数据流
  13. c程序设计语言如何补零,C语言程序设计(补)-中国大学mooc-题库零氪
  14. End-to-End Object Detection with Transformers的部分解读
  15. Swift 反射 API 及用法
  16. UE4分支的Git Flow
  17. 【日本软件外包】设计书中常用到的文型
  18. 拥塞避免算法、快重传、快恢复、慢启动
  19. IOS开发 百度语音实现播报及IOS12.1后的播报功能问题与实现
  20. 软件测试简历中的项目应该如何准备?

热门文章

  1. ZeroC ICE 源码编译
  2. sync是同步还是非同步_1588v2,是怎样实现时钟同步的?
  3. 【市场分析1】FinTech之香港虚拟银行VB
  4. docker推送镜像至阿里私有镜像仓库
  5. 【2016读书】4月读书笔记
  6. 苹果客服:iPhone 12又一重磅升级确认!
  7. Python 基础学习02
  8. 【单片机】STM32 最小板 学习笔记
  9. Python OpenCV 图像处理之 图像运算和图像位运算知识补充
  10. 计算机重装系统的作用,经常重装系统对电脑有影响吗?听听专家怎么说!