资源描述
Linux系统调用分析
sys_sysfs
sys_creat
sys_llseek
sys_flock
9811552
周 天
9811553
郏方贵
9811554
阙雪松
9811562
冯利民
目录
目录 2
Linux系统调用分析 3
第一章 Linux文件系统概述 3
一、VFS文件系统 3
二、安装的文件系统的结构 4
三、打开文件的有关数据结构 4
第二章 sys_sysfs 系统调用 5
一、sysfs 系统调用综述 6
二、sysfs系统调用涉及的数据结构和函数说明: 7
第三章 sys_creat系统调用分析 8
一、sys_creat系统调用综述 8
二、创建文件的操作过程 11
三、sys_open 所涉及的数据结构 9
四、相关函数说明 10
附录:源代码
致谢 18
Linux系统调用分析
摘要:本文是对Linux的两个文件系统调用:sys_sysfs、sys_creat、进行了好的分析。我们一开始是对Linux文件系统做一个简要的介绍,再对这二个文件系统调用的实现进行分析。
Linux一个最大的特点是它支持多种文件系统,如:EXT、EXT2、XIA、MINIX、UMSDOS、MSDOS、VFAT、PROC、SMB、NCP、ISO9660、SYSV、HPFS、SFFS和UFS等等,甚至还支持NFS。它之所以能支持这么多的文件系统,是由于它在具体的文件系统上增加了一层抽象层:VFS文件系统。VFS文件系统将独立于具体文件系统的数据和操作集中在自身之中,并通过数据结构中的UNION类型和函数指针将具体的文件系统包容进来。这种分层的概念,使得Linux不仅可以有良好的兼容性,而且也使它有较大的可扩充性。
Linux的文件系统不但可以建立在多种具体的文件系统之上,它还具有较高的性能。这是由它丰富而复杂的Cache机制来保证的。这里的Cache机制指的是文件系统中涉及的各种缓冲区。Linux的文件系统中的缓冲系统有:Inode缓冲(Inode Cache)、目录缓冲(Directory Cache)、Buffer缓冲(Buffer Cache,Linux系统中最为复杂的缓冲之一)和页面缓冲(Page Cache,在存储管理模块中);另外还有各种小型的缓冲系统。这多种缓冲系统各司其职,又构成层次体系,如Buffer缓冲在文件系统的最底层,负责与设备交互,而页面缓冲就处于Buffer缓冲之上。这种复杂的缓冲层次体系有效地弥补了文件系统中内存与外存之间的速度差异,提高了文件系统的性能。
下面,本文先将整个文件系统的概况和一些重要的概念、数据结构介绍一下,然后再讨论文件读写操作的有关内容。
第一章 Linux文件系统概述
Linux的文件系统分为两个层次:VFS文件系统和某个具体的文件系统,如典型的EXT2。
一、VFS文件系统
VFS文件系统是建立在具体文件系统上的一个抽象层次。它必须管理安装(mount)在Linux系统中的每一个具体的文件系统;为此,它维护着众多的数据结构,这些数据结构描述了整个文件系统和实际的安装上的文件系统。
VFS采用的概念同UNIX文件系统相似,即用超级块(SuperBlock)和Inode节点来描述和管理文件系统。超级块是整个文件系统的管理数据,而Inode则描述了单个文件或目录。
图1.上面显示了Linux文件系统的组成结构,VFS处于最上层,Buffer Cache处于最下层,负责与设备的交互。Inode Cache和Directory Cache为整个文件系统提供服务。
二、安装的文件系统的结构
下图显示了一个文件系统安装后,它的超级块在VFS文件系统中的位置和相关的数据结构:
图2文件安装数据结构
上图中涉及到三个表:
vfsmount表。这个表是一个链表,它登记了VFS文件系统中当前安装的文件系统。
super_block表。这个表是一个数组,它存放了系统当前安装的所有文件系统的超级块。
file_system_type表。这也是一个链表,它登记了VFS文件系统中当前安装的文件系统的类型信息。
vfsmount表中节点的mnt_sb指向super_block表中的元素。而super_block表中元素的s_type指向File_system_type表中的节点。
三、打开文件的有关数据结构
一个文件在打开以后,在系统中就要为它建立起相应的数据结构。主要有两个:一是在当前进程的打开文件描述符中为它分配一项,二是分配一个文件结构(file struct,即FILE结构),并将文件描述符与该结构对应起来。FILE结构再指向该文件的inode。
这样的结构,包括在文件描述符和inode之间加一层FILE结构,主要是为了实现多种方式的共享。在FILE结构中,有文件读写的指针和文件操作的函数指针等信息。文件共享有两种方式:一是共享inode节点,是多个FILE结构共享一个inode;二是共享FILE结构,是多个文件描述符共享一个FILE结构,实际上是多个进程(必须是父子进程)共享一个打开的文件,尤其是它的读写指针。前一种共享实际是通过文件的链接(link)来实现的,两个无亲缘关系的进程打开同一个文件也属于这种共享。后一种共享只有在父子进程之间才会发生。当执行fork()系统调用后产生一个子进程,子进程将父进程的文件描述符复制到子进程中,这样,子进程和父进程的打开文件表是一样的,都指向同一个FILE结构,共享其文件读写指针。(关于文件打开过程的有关内容,参见李明的报告)。下图显示了打开文件的数据结构之间的联系。
图5打开文件的数据结构之间的关系
第二章 sys_sysfs 系统调用
一、sysfs 系统调用综述
文件: fs/super.c
调用形式:int sys_fs(int option, …)
此系统调用仅仅用于UNIX System V,它读取文件系统列表,返回系统所注册的文件系统的信息。系统调用根据可变参数的返回特定的信息。它的功能有下列三种形式:
1.返回可变参数中指定的描述的文件系统在已注册文件系统列表中的序号。它的形式是:
int sysfs(int option, char * name)
此时的第一个参数option=1。
2.第二种形式有三个参数,可变参数为序号index和用于存储对应这个序号的文件系统的名称的字符缓冲区。
它的形式为:
int sysfs(int option, int index, char * name)
此时的第一个参数option=2。
3.返回操作系统中注册的文件系统的个数。
此时的第一个参数option=3。
二、sysfs系统调用涉及的数据结构和函数说明:
1. file_system_type数据结构
文件系统类型的注册和注销反映在以file_systems(fs/super.c)为链头,file_system_type为节点的单向链表中。注册表的每一个file_system_type节点描述一个已注册的文件系统类型。
static struct file_system_type *file_systems =
(struct file_system_type *) NULL;
struct file_system_type {
struct super_block *(*read_super)(struct super_block *,void *,int);
/* read_super所指的函数用于读出该文件系统在外存的超级块 */
const char *name;
/* 文件系统的类型名,如ext2 */
int requires_dev;
/* 支持文件系统的设备,proc文件系统不需要任何设备 */
struct file_system_type * next;
/* 文件系统类型链表的后续指针 */
};
2. fs_index(const char* __name)
此函数返回__name所指的字符串所代表的文件系统在操作系统所注册文件系统列表中序号。
源代码://from super.c
static int fs_index(const char * __name)
{
struct file_system_type * tmp;
char * name;
int err, index;
//将所查询的文件系统的名称拷贝到核心态内存空间中(防止用户态内存空间中的数据
//换出)。
err = getname(__name, &name);
if (err)
return err;
index = 0;
//遍历已注册的文件系统列表file_systems,找到与所查找的文件系统同名的文件系统
//若操作成功返回其序号
for (tmp = file_systems ; tmp ; tmp = tmp->next) {
if (strcmp(tmp->name, name) == 0) {
//操作成功,释放所分配的核心态内存
putname(name);
//返回这个文件系统在已注册的文件系统中的序号
return index;
}
index++;
}
//操作不成功,释放所分配的核心态内存
putname(name);
//返回出错信息
return -EINVAL;
}
3. fs_name(int index, char* buf)
此函数输入文件系统在操作系统所注册文件系统列表中序号,返回这个序号对应的文件系统的名字,并存入字符串缓冲区buf。
源代码:
static int fs_name(unsigned int index, char * buf)
{
struct file_system_type * tmp;
int err, len;
tmp = file_systems;
//遍历文件系统列表,找到第index个文件系统
while (tmp && index > 0) {
tmp = tmp->next;
index--;
}
//如果找不到,返回错误信息
if (!tmp)
return -EINVAL;
len = strlen(tmp->name) + 1; //文件系统的名称的长度
//以buf开头的len 长度的内存空间是否可写?
err = verify_area(VERIFY_WRITE, buf, len);
if (err)
return err;
//拷贝文件系统的名称
memcpy_tofs(buf, tmp->name, len);
return 0;
}
4. fs_maxindex()
此函数返回操作系统中所注册的文件系统的个数。
源代码:
static int fs_maxindex(void)
{
struct file_system_type * tmp;
int index;
index = 0;
//遍历已注册的文件系统列表,返回已注册的文件系统的总数
for (tmp = file_systems ; tmp ; tmp = tmp->next)
index++;
return index;
}
第三章 sys_creat系统调用分析
一、sys_creat系统调用综述
sys_creat系统调用的功能、输入参数、输出参数及其意义如下:
int sys_creat(const char * pathname, int mode)
{
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
功能:在指定的目录路径下,用指定的文件名和操作模式创建一个文件。
输入:文件名(pathname)和文件的操作模式(权限)
输出:若操作成功,返回所创建的文件的文件描述符(file descriptor)。
若操作失败,返回一个小于0的数。各个返回的出错数及其意义如下:
出错参数
意义
-EMFILE
打开文件过多
-ENFILE
文件表溢出
-ENOMEM
内存不足
-EFAULT
无效的内存地址
-ENOENT
没有这样的文件或目录
二、创建文件的操作过程
创建文件过程
sys_creat系统调用的功能是根据指定的文件名(包含目录)和操作模式创建一个文件。具体的操作过程如下:
(1). 调用get_unused_fd()从进程的可使用的文件描述符中找出一个没有被使用的文件描述符(确切的说是文件描述符数组的下标)。
(2). 调用flip_open()打开一个创建的文件,并返回一个文件描述符。
(3). 调用fd_install()将新创建的文件的文件描述符拷贝到进程的打开文件表中。
三、sys_open 所涉及的数据结构
1.资源限制
struct rlimit {
long rlim_cur;
long rlim_max;
};
对于任何一个进程,它所能支配的每一种资源都有两个限制,当前限制(rlim_cur)与最大限制(rlim_max)。一个进程可以将当前的资源限制的值设为0到最大限制的任何值,同时它也可以将最大限制缩小到当前限制。对于单个进程而言,NR_OPEN(通常是256)是极限限制,这个极限限制对可打开的文件资源当前限制和最大限制均有效。但是,对于每一个进程的可打开文件数目而言,还有一个上限,即文件描述符集的大小(__FD_SETSIZE = 1024,即NR_OPEN<=__FD_SETSIZE)。同时,整个操作系统的所有进程(包括用户进程和核心进程)所能打开的文件的总数也是有限制的(NR_FILES=4096)。
2.进程所打开的文件系统的信息
struct files_struct {
int count;
fd_set close_on_exec;
fd_set open_fds;
struct file * fd[NR_OPEN];
};
其中close_on_exec为当进程调用exec()是,新进程需要关闭的文件描述符集合中所指的文件;open_fds为当前进程已经打开的文件的描述符的集合;fd为进程的文件指针的数组,即其所有的文件描述符。
进程打开或创建一个文件就是从open_fds位图(bitmap)中找到一个等于0的位,这一位对应的序号即表示文件描述符数组中对应下标的文件描述符没有被使用。
3.文件信息
整个系统运行期间维护一张以struct file作为节点的双向链表。表头由first_file给出。每个节点对应一个已打开的文件,包含了此文件的inode、操作函数等。对文件的所有操作都离不开系统打开文件表。
struct file *first_file = NULL; /* fs/file_table.c */ //链表首指针
struct file {
struct file *f_next, **f_pprev; //双向链表指针
struct dentry *f_dentry; //目录项指针
struct file_operations *f_op; //文件的操作函数的结构指针
mode_t f_mode; //文件操作模式
loff_t f_pos; //文件读写头位置
unsigned int f_count, f_flags; //文件引用记数和操作标志
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct f_owner; //文件所有权信息
unsigned int f_uid, f_gid; //文件的用户标志和组标志
int f_error;
unsigned long f_version;
/* needed for tty driver, and maybe others */
void *private_data;
};
4.进程所处的文件系统的信息。
struct fs_struct {
int count;
unsigned short umask;
struct inode * root, * pwd;
};
其中,root 为系统的根目录,pwd为当前进程的工作目录。
5.进程结构
struct task_struct {
…
/* limits */
struct rlimit rlim[RLIM_NLIMITS];
struct fs_struct *fs;
…
/* open file information */
struct files_struct *files;
…
};
其中,rlim为进程所能支配的所有资源;fs为进程运行时的文件系统的相关信息;files为与进程打开的文件的信息。
四、相关函数说明
1.int get_unused_fd()
此函数从当前进程的打开文件表中找出一个没有被使用的文件描述符在进程的文件描述符数组(位图)中的下标。
(1). 步骤:
a. 从进程描述符位图中找出一个没有被占用(置位)的位,得到它的下标。
b. 检验这个描述符下标的合法性,即是否满足资源限制。
c. 若操作成功,返回这个下标。
(2). 源代码:
/* Find an empty file descriptor entry, and mark it busy. */(fs/open.c)
int get_unused_fd(void)
{
struct files_struct * files = current->files;
int fd, error;
repeat:
error = -EMFILE;
//从文件描述符集合中找到一个没有被使用的位的下标
fd = find_next_zero_bit(files->open_fds,
current->files->max_fdset,
files->next_fd);
/*
N.B. For clone tasks sharing a files structure, this test
will limit the total number of files that can be opened.
*/
//查看资源限制
if (fd >= current->rlim[RLIMIT_NOFILE].rlim_cur)
goto out;
/* Do we need to expand the fdset array? */
if (fd >= current->files->max_fdset) {
error = expand_fdset(files, 0);
if (!error)
goto repeat;
goto out;
}
/*
Check whether we need to expand the fd array.
*/
if (fd >= files->max_fds) {
error = expand_fd_array(files, 0);
if (!error)
goto repeat;
goto out;
}
//设置对应的文件描述符的位
FD_SET(fd, files->open_fds);
//将close_on_exec标志清零
FD_CLR(fd, files->close_on_exec);
files->next_fd = fd + 1;
#if 1 /* Sanity check */
if (files->fd[fd] != NULL) {
printk(“get_unused_fd: slot %d not NULL!\n”, fd);
files->fd[fd] = NULL; }
#endif
error = fd;
out:
#ifdef FDSET_DEBUG
if (error < 0)
printk (KERN_ERR __FUNCTION__ “: return %d\n”, error);
#endif
return error;
}
2. inline void put_unused_fd(unsigned int fd)
此函数将一个文件描述符释放,即在文件描述符位图中,将fd对应的位清零。
(1). 步骤:
a. 将fd对应的文件描述符在文件描述符位图中清零;
b. 修改这个文件的相关数据。
(2). 源代码:(fs/open.c)
inline void put_unused_fd(unsigned int fd)
{
FD_CLR(fd, current->files->open_fds); // fd对应的文件描述符位清零
if (fd < current->files->next_fd) //修改数据
current->files->next_fd = fd;
}
1. inline void fd_install(unsigned int fd, struct file *file)
此函数将文件指针file填到进程的打开文件表中的第fd项。
源代码:(fs/open.c)
/*
Install a file pointer in the fd array.
*/
extern inline void fd_install(unsigned int fd, struct file *file)
{
current->files->fd[fd] = file;
}
4.struct file *filp_open(const char * filename, int flags, int mode)
注意:在2.0.XX版本中,这个函数是do_open()
(1). 步骤:
a. 申请一个文件结构的内存空间;
b. 对文件操作模式(mode)和标志(flags)进行修改;
c. open_namei调用dir_namei查找文件所在目录的inode,并得到不带路径的文件名及其长度。若是要新创建文件,则调用具体文件系统的create操作并返回。
d. 填充这个文件结构。
(2). 源代码:(fs/open.c)
struct file *filp_open(const char * filename, int flags, int mode)
{
struct inode * inode;
struct dentry * dentry;
struct file * f;
int flag,error;
error = -ENFILE; //错误:文件表溢出
f = get_empty_filp(); //申请一个文件描述的内存空间,
if (!f) //操作不成功(内存不足)
goto out;
f->f_flags = flag = flags;
f->f_mode = (flag+1) & O_ACCMODE;
if (f->f_mode)
flag++;
if (flag & O_TRUNC)
flag |= 2;
//返回与输入文件名对应的不包含目录的文件名并将相关数据存入dentry中
dentry = open_namei(filename,flag,mode);
error = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto cleanup_file;
inode = dentry->d_inode;
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_dentry;
}
//设置打开文件的相关参数
f->f_dentry = dentry; //dentry
f->f_pos = 0; //读写头位置
f->f_reada = 0;
f->f_op = NULL; //操作函数
if (inode->i_op)
f->f_op = inode->i_op->default_file_ops;
if (f->f_op && f->f_op->open) {
error = f->f_op->open(inode,f);
if (error)
goto cleanup_all;
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
return f;
cleanup_all:
if (f->f_mode & FMODE_WRITE)
put_write_access(inode);
cleanup_dentry:
f->f_dentry = NULL;
dput(dentry); //释放内存
cleanup_file:
put_filp(f); //释放文件描述内存
out:
return ERR_PTR(error); //返回出错信息
}
5. struct dentry * open_namei(const char * pathname, int flag, int mode)
(fs/namei.c)
open_namei调用dir_namei查找文件所在目录的inode,并得到不带路径的文件名及其长度。若是要新创建文件,则调用具体文件系统的create操作并返回。对于ext2文件系统而言,就是ext2_create。否则调用lookup和follow_link找到文件的inode节点。接着检查inode权限,对于不同文件类型调整flag。如果要截断文件,则在获取写权限后,做do_truncate截断文件。最终获得文件inode节点及不带路径的文件名及其长度。
附录:源代码
一、sysfs系统调用的源代码
/*
* Whee.. Weird sysv syscall.
*/
asmlinkage int sys_sysfs(int option, ...)
{
va_list args;
int retval = -EINVAL;
unsigned int index;
va_start(args, option); //设置可变参数列表的指针
switch (option) {
case 1:
//从可变参数列表中读出一个字符串作为fs_index的参数
//返回以这个字符串为名称的文件系统在文件系统列表file_systems中的序号
retval = fs_index(va_arg(args, const char *));
break;
case 2:
//从可变参数列表中读出一个整型数index
index = va_arg(args, unsigned int);
//从系统文件系统列表file_systems中,读出第index个文件系统的名字
retval = fs_name(index, va_arg(args, char *));
break;
case 3:
//返回系统文件列表file_systems中注册的文件系统的个数
retval = fs_maxindex();
break;
}
//清除可变参数列表的指针
va_end(args);
return retval;
}
二、sys_creat系统调用的源代码
由于sys_creat系统调用是通过sys_open系统调用实现的,下面的分析通过sys_open来说明。
2. sys_open 的源代码
asmlinkage int sys_open(const char * filename, int flags, int mode)
{
char * tmp;
int fd, error;
//文件名从用户空间拷贝到核心空间
tmp = getname(filename);
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {//如果没有出错
lock_kernel();//取得核心锁
fd = get_unused_fd(); //找出一个没有使用的文件描述符
if (fd >= 0) {
//打开文件,返回一个文件指针
struct file * f = filp_open(tmp, flags, mode);
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
//将文件指针拷贝到进程打开的文件描述符表中
fd_install(fd, f);
}
out:
unlock_kernel(); //释放核心锁
putname(tmp); //释放所申请的核心内存
}
return fd; //返回文件描述符
out_error: //出错
put_unused_fd(fd); //释放所申请的文件描述符
fd = error; //返回一个出错信息
goto out;
}
致谢
首先感谢感谢陈世强老师为我们提供的这次分析Linux源代码的机会,以及他对于我们的指导,正是这样一个机会,使我们能够静下心来分析这种晦涩难懂的源代码。
其次要感谢同组同学的通力合作,因为没有同学的通力合作是难以分析这样的源代码的。
16
展开阅读全文