Linux0.11 文件打开open函数(五)

简介: Linux0.11 文件打开open函数(五)

前言

  文件系统根目录下的所有文件名信息则保持在指定 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;
  else
    return 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)
{
    //...
}


目录
相关文章
|
2天前
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
40 19
|
16天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
53 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
4天前
|
Linux Android开发 开发者
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
34 18
|
2天前
|
Ubuntu Linux Go
golang编译成Linux可运行文件
本文介绍了如何在 Linux 上编译和运行 Golang 程序,涵盖了本地编译和交叉编译的步骤。通过这些步骤,您可以轻松地将 Golang 程序编译成适合 Linux 平台的可执行文件,并在目标服务器上运行。掌握这些技巧,可以提高开发和部署 Golang 应用的效率。
44 14
|
1天前
|
存储 NoSQL Linux
linux积累-core文件是干啥的
核心文件是Linux系统在程序崩溃时生成的重要调试文件,通过分析核心文件,开发者可以找到程序崩溃的原因并进行调试和修复。本文详细介绍了核心文件的生成、配置、查看和分析方法
17 6
|
3天前
|
存储 NoSQL Linux
linux之core文件如何查看和调试
通过设置和生成 core 文件,可以在程序崩溃时获取详细的调试信息。结合 GDB 等调试工具,可以深入分析 core 文件,找到程序崩溃的具体原因,并进行相应的修复。掌握这些调试技巧,对于提高程序的稳定性和可靠性具有重要意义。
42 6
|
12天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
68 13
|
1月前
|
Linux 开发工具 Perl
在Linux中,有一个文件,如何删除包含“www“字样的字符?
在Linux中,如果你想删除一个文件中包含特定字样(如“www”)的所有字符或行,你可以使用多种文本处理工具来实现。以下是一些常见的方法:
42 5
|
1月前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
53 6
|
7月前
|
Linux 开发者
Linux文件编程(open read write close函数)
通过这些函数,开发者可以在Linux环境下进行文件的读取、写入和管理。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
177 4