文件的操作主要包括了文件的打开关闭和读写.在这节中主要分析了linux内核中的文件操作的实现.还是同前两节一样,涉及到块设备与页面缓存的部份先放一边.后续有会有专题分析与此相关的内容.

二:文件的打开

在用户空间的,打开文件常用的api是open().它的系统调用入口是sys_open():

.asmlinkage long sys_open(const char __user * filename, int flags, int mode)

{

char * tmp;

int fd, error;

#if BITS_PER_LONG != 32

flags |= O_LARGEFILE;

#endif

//从用户空间copy值

tmp = getname(filename);

fd = PTR_ERR(tmp);

if (!IS_ERR(tmp)) {

//分配一个没有被使用的fd

fd = get_unused_fd();

if (fd >= 0) {

//取得与文件相关的file结构

struct file *f = filp_open(tmp, flags, mode);

error = PTR_ERR(f);

if (IS_ERR(f))

goto out_error;

//将file 添加file_struct中的fd数组的相应项

fd_install(fd, f);

}

out:

//释放分配的内存空间

putname(tmp);

}

return fd;

out_error:

put_unused_fd(fd);

fd = error;

goto out;

}

与进程相关的文件系统结构在<>已经分析过了.如有不太清楚的可以自行参阅这篇文章.

首先在进程中取得一个没有被使用的文件描述符.这是在get_unused_fd()中完成的.它的代码如下:

int get_unused_fd(void)

{

struct files_struct * files = current->files;

int fd, error;

error = -EMFILE;

spin_lock(&files->file_lock);

repeat:

//取得files->open_fds->fds_bits中下一个没有使用的位

fd = find_next_zero_bit(files->open_fds->fds_bits,

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? */

//max_fdset: 位图位的总数

//如果超过了位图的总数

if (fd >= files->max_fdset) {

error = expand_fdset(files, fd);

if (!error) {

error = -EMFILE;

goto repeat;

}

goto out;

}

/*

* Check whether we need to expand the fd array.

*/

//如果超过了所描述对象的总数

if (fd >= files->max_fds) {

//扩充文件描述对象数组

error = expand_fd_array(files, fd);

if (!error) {

error = -EMFILE;

goto repeat;

}

goto out;

}

//在open_fds置该位

FD_SET(fd, files->open_fds);

//在close_on_exec中清除该位.表示如果调用exec()执行一个新程序的时候不需要关闭这个

//文件描述符

FD_CLR(fd, files->close_on_exec);

files->next_fd = fd + 1;

#if 1

/* Sanity check */

//如果在fd中的相应项不为NULL 将其置NULL

if (files->fd[fd] != NULL) {

printk(KERN_WARNING "get_unused_fd: slot %d not NULL!\n", fd);

files->fd[fd] = NULL;

}

#endif

error = fd;

out:

spin_unlock(&files->file_lock);

return error;

}

如果文件描述符位图空间不够或者文件对象描述符数组空间不够.就会调用expand_fdset()和expand_fd_array()进行空间的扩展.代码分别如下所示:

int expand_fdset(struct files_struct *files, int nr)

{

fd_set *new_openset = NULL, *new_execset = NULL;

int error, nfds = 0;

error = -EMFILE;

//超过了总限制

if (files->max_fdset >= NR_OPEN || nr >= NR_OPEN)

goto out;

//现在文件描述符的最大值

nfds = files->max_fdset;

spin_unlock(&files->file_lock);

/* Expand to the max in easy steps */

//如果现在的文件描述符数目少于8个page大小,则扩展到8个page

//否则将其扩大两倍.其值不能超过规定的最大值

do {

if (nfds < (PAGE_SIZE * 8))

nfds = PAGE_SIZE * 8;

else {

nfds = nfds * 2;

if (nfds > NR_OPEN)

nfds = NR_OPEN;

}

} while (nfds <= nr);

//分新配大小分配存储空间

error = -ENOMEM;

new_openset = alloc_fdset(nfds);

new_execset = alloc_fdset(nfds);

spin_lock(&files->file_lock);

if (!new_openset || !new_execset)

goto out;

error = 0;

/* Copy the existing tables and install the new pointers */

//将旧值copy到新分配的空间内.并将剩余空间置为0

//新新空间挂载到进程的file中.并释放旧空间

if (nfds > files->max_fdset) {

int i = files->max_fdset / (sizeof(unsigned long) * 8);

int count = (nfds - files->max_fdset) / 8;

/*

* Don't copy the entire array if the current fdset is

* not yet initialised.

*/

//copy和剩余段置零的过程

if (i) {

memcpy (new_openset, files->open_fds, files->max_fdset/8);

memcpy (new_execset, files->close_on_exec, files->max_fdset/8);

memset (&new_openset->fds_bits[i], 0, count);

memset (&new_execset->fds_bits[i], 0, count);

}

//交换新旧空晨

nfds = xchg(&files->max_fdset, nfds);

new_openset = xchg(&files->open_fds, new_openset);

new_execset = xchg(&files->close_on_exec, new_execset);

spin_unlock(&files->file_lock);

//将旧空间释放掉

free_fdset (new_openset, nfds);

free_fdset (new_execset, nfds);

spin_lock(&files->file_lock);

return 0;

}

/* Somebody expanded the array while we slept ... */

out:

spin_unlock(&files->file_lock);

if (new_openset)

free_fdset(new_openset, nfds);

if (new_execset)

free_fdset(new_execset, nfds);

spin_lock(&files->file_lock);

return error;

}

expand_fd_array()的代码如下:

int expand_fd_array(struct files_struct *files, int nr)

{

struct file **new_fds;

int error, nfds;

error = -EMFILE;

if (files->max_fds >= NR_OPEN || nr >= NR_OPEN)

goto out;

//取得现在的文件描述对象数

nfds = files->max_fds;

spin_unlock(&files->file_lock);

/*

* Expand to the max in easy steps, and keep expanding it until

* we have enough for the requested fd array size.

*/

//设置新的描述对象数值

do {

#if NR_OPEN_DEFAULT < 256

if (nfds < 256)

nfds = 256;

else

#endif

if (nfds < (PAGE_SIZE / sizeof(struct file *)))

nfds = PAGE_SIZE / sizeof(struct file *);

else {

nfds = nfds * 2;

if (nfds > NR_OPEN)

nfds = NR_OPEN;

}

} while (nfds <= nr);

error = -ENOMEM;

new_fds = alloc_fd_array(nfds);

spin_lock(&files->file_lock);

if (!new_fds)

goto out;

/* Copy the existing array and install the new pointer */

//copy和设置剩余空间的过程,并将新旧空间交换.操作完成过后,释放旧空间

if (nfds > files->max_fds) {

struct file **old_fds;

int i;

old_fds = xchg(&files->fd, new_fds);

i = xchg(&files->max_fds, nfds);

/* Don't copy/clear the array if we are creating a new

fd array for fork() */

if (i) {

memcpy(new_fds, old_fds, i * sizeof(struct file *));

/* clear the remainder of the array */

memset(&new_fds[i], 0,

(nfds-i) * sizeof(struct file *));

spin_unlock(&files->file_lock);

free_fd_array(old_fds, i);

spin_lock(&files->file_lock);

}

} else {

/* Somebody expanded the array while we slept ... */

spin_unlock(&files->file_lock);

free_fd_array(new_fds, nfds);

spin_lock(&files->file_lock);

}

error = 0;

out:

return error;

}

取得空闲文件描述符之后,将取得与文件对应的file.将file与文件对象符关联起来的操作是在fd_install()关联起来的.它的代码如下:

void fastcall fd_install(unsigned int fd, struct file * file)

{

struct files_struct *files = current->files;

spin_lock(&files->file_lock);

//如果相应项已经有对象了.则是一个BUG

if (unlikely(files->fd[fd] != NULL))

BUG();

//将file添加至对象描述符数组

files->fd[fd] = file;

spin_unlock(&files->file_lock);

}

Sys_open()核心的操作是取得与文件相对应的file.这个操作是在filp_open()中完成的.它的代码如下:

/*

* Note that while the flag value (low two bits) for sys_open means:

*   00 - read-only

*   01 - write-only

*   10 - read-write

*   11 - special

* it is changed into

*   00 - no permissions needed

*   01 - read-permission

*   10 - write-permission

*   11 - read-write

* for the internal routines (ie open_namei()/follow_link() etc). 00 is

* used by symlinks.

*/

struct file *filp_open(const char * filename, int flags, int mode)

{

int namei_flags, error;

struct nameidata nd;

//因为在sys_open对flag的定义如filp_open的定义不相同。因此要把两者的flag进行转换

namei_flags = flags;

//转换低两位

if ((namei_flags+1) & O_ACCMODE)

namei_flags++;

//O_TRUNC:表示需要截尾,因此如果O_TRUNC被置是需要写权限的

if (namei_flags & O_TRUNC)

namei_flags |= 2;

//取得文件结点对应的nameidata.如果节点不存在,则新建之

error = open_namei(filename, namei_flags, mode, &nd);

if (!error)

//将文件节点对应的nameidata转换为file

return dentry_open(nd.dentry, nd.mnt, flags);

return ERR_PTR(error);

}

这段代码要注意作者附加给的注释.在sys_open与filp_open()中标志位定义有些不相同.所示有必须对标志进行相应的转换.

转进去看一下open_namei()的操作.代码如下:

{

int acc_mode, error = 0;

struct dentry *dentry;

struct dentry *dir;

int count = 0;

acc_mode = ACC_MODE(flag);

/* Allow the LSM permission hook to distinguish append

access from general write access. */

//附加模式

if (flag & O_APPEND)

acc_mode |= MAY_APPEND;

/* Fill in the open() intent data */

nd->intent.open.flags = flag;

nd->intent.open.create_mode = mode;

/*

* The simplest case - just a plain lookup.

*/

//O_CREAT:如果文件不存在.则新建之

//如果没有定义O_CREAT标志.只要查找文件系统中结点是否存在就可以了

if (!(flag & O_CREAT)) {

error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);

if (error)

return error;

goto ok;

}

/*

* Create - we need to know the parent.

*/

//如果定义了O_CREAT标志.则先查找父结点

error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);

if (error)

return error;

/*

* We have the parent and last component. First of all, check

* that we are not asked to creat(2) an obvious directory - that

* will not do.

*/

error = -EISDIR;

//判断查找是否成功

if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len])

goto exit;

dir = nd->dentry;

nd->flags &= ~LOOKUP_PARENT;

down(&dir->d_inode->i_sem);

//到父目录中查找是否有该结点.如果没有该结点就会创建相应的dentry但dentry->d_inode为空

dentry = __lookup_hash(&nd->last, nd->dentry, nd);

do_last:

error = PTR_ERR(dentry);

//查找错误,出错返回

if (IS_ERR(dentry)) {

up(&dir->d_inode->i_sem);

goto exit;

}

/* Negative dentry, just create the file */

//dentry->d_inode为空.说明这个结点是新建的

if (!dentry->d_inode) {

if (!IS_POSIXACL(dir->d_inode))

mode &= ~current->fs->umask;

error = vfs_create(dir->d_inode, dentry, mode, nd);

up(&dir->d_inode->i_sem);

dput(nd->dentry);

nd->dentry = dentry;

if (error)

goto exit;

/* Don't check for write permission, don't truncate */

acc_mode = 0;

flag &= ~O_TRUNC;

goto ok;

}

/*

* It already exists.

*/

//结点原本就存在的情况

up(&dir->d_inode->i_sem);

error = -EEXIST;

if (flag & O_EXCL)

goto exit_dput;

//如果是挂载目录.则跳转到挂载文件系统的根目录

if (d_mountpoint(dentry)) {

error = -ELOOP;

if (flag & O_NOFOLLOW)

goto exit_dput;

while (__follow_down(&nd->mnt,&dentry) && d_mountpoint(dentry));

}

error = -ENOENT;

//对异常情况的排除

if (!dentry->d_inode)

goto exit_dput;

//如果结点是一个符号链接

if (dentry->d_inode->i_op && dentry->d_inode->i_op->follow_link)

goto do_link;

dput(nd->dentry);

nd->dentry = dentry;

error = -EISDIR;

//如果结点是一个目录,出错退出

if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))

goto exit;

ok:

//对打开文件进行的各项统一处理

error = may_open(nd, acc_mode, flag);

if (error)

goto exit;

return 0;

exit_dput:

dput(dentry);

exit:

path_release(nd);

return error;

do_link:

error = -ELOOP;

if (flag & O_NOFOLLOW)

goto exit_dput;

/*

* This is subtle. Instead of calling do_follow_link() we do the

* thing by hands. The reason is that this way we have zero link_count

* and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT.

* After that we have the parent and last component, i.e.

* we are in the same situation as after the first path_walk().

* Well, almost - if the last component is normal we get its copy

* stored in nd->last.name and we will have to putname() it when we

* are done. Procfs-like symlinks just set LAST_BIND.

*/

nd->flags |= LOOKUP_PARENT;

error = security_inode_follow_link(dentry, nd);

if (error)

goto exit_dput;

touch_atime(nd->mnt, dentry);

nd_set_link(nd, NULL);

error = dentry->d_inode->i_op->follow_link(dentry, nd);

if (!error) {

char *s = nd_get_link(nd);

if (s)

error = __vfs_follow_link(nd, s);

if (dentry->d_inode->i_op->put_link)

dentry->d_inode->i_op->put_link(dentry, nd);

}

dput(dentry);

if (error)

return error;

nd->flags &= ~LOOKUP_PARENT;

if (nd->last_type == LAST_BIND) {

dentry = nd->dentry;

goto ok;

}

error = -EISDIR;

if (nd->last_type != LAST_NORM)

goto exit;

if (nd->last.name[nd->last.len]) {

putname(nd->last.name);

goto exit;

}

error = -ELOOP;

if (count++==32) {

putname(nd->last.name);

goto exit;

}

dir = nd->dentry;

down(&dir->d_inode->i_sem);

dentry = __lookup_hash(&nd->last, nd->dentry, nd);

putname(nd->last.name);

goto do_last;

}

在这里忽略了结点为符号链接的情况,这种情况下就是找到符号链接的路径,然后重新进行一次相同的操作而已经.我们把注意力主要放在一般的文件操上.

在这里,对于已存在文件和要新建的文件有着不同的处理,只要是新创建文件会调用vfs_create()处理.其代码如下:

int vfs_create(struct inode *dir, struct dentry *dentry, int mode,

struct nameidata *nd)

{

//创建文件之前的检查.(在sys_mkdir()的时候已经分析过个函数)

int error = may_create(dir, dentry, nd);

if (error)

return error;

//如果文件系统不允许creat

if (!dir->i_op || !dir->i_op->create)

return -EACCES;    /* shouldn't it be ENOSYS? */

mode &= S_IALLUGO;

mode |= S_IFREG;

error = security_inode_create(dir, dentry, mode);

if (error)

return error;

DQUOT_INIT(dir);

//调用父结点对应的create操作

error = dir->i_op->create(dir, dentry, mode, nd);

if (!error) {

//如果创建成功,则发出通知

inode_dir_notify(dir, DN_CREATE);

security_inode_post_create(dir, dentry, mode);

}

return error;

}

要这里,我们可以看到,它会调用父目录结点的creat操作来创建结点.等分析完sys­_open()操作之后,再转入具体的文件系统进行分析.

不管是新建的结点还是已经建立的结点,都会进入到may_open()中进行处理.其代码如下所示:

int may_open(struct nameidata *nd, int acc_mode, int flag)

{

struct dentry *dentry = nd->dentry;

struct inode *inode = dentry->d_inode;

int error;

//结点所对应的inode不存在

if (!inode)

return -ENOENT;

//是一个链接或者是目录的情况

if (S_ISLNK(inode->i_mode))

return -ELOOP;

if (S_ISDIR(inode->i_mode) && (flag & FMODE_WRITE))

return -EISDIR;

//检查是否有相应的权限

error = permission(inode, acc_mode, nd);

if (error)

return error;

/*

* FIFO's, sockets and device files are special: they don't

* actually live on the filesystem itself, and as such you

* can write to them even if the filesystem is read-only.

*/

//如果是FIFO和SOCK文件,则将O_TRUNC标志去掉

if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {

flag &= ~O_TRUNC;

} else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {

//如果是一个块设备文件或者是一个字符设备文件,却挂载选项带有MNT_NODEV

//标志.出错退出

if (nd->mnt->mnt_flags & MNT_NODEV)

return -EACCES;

flag &= ~O_TRUNC;

} else if (IS_RDONLY(inode) && (flag & FMODE_WRITE))

//操作是可写出,但结点又是只读的.出错退出

return -EROFS;

/*

* An append-only file must be opened in append mode for writing.

*/

//如果节点是append模式的,则必须要以append模式打开

if (IS_APPEND(inode)) {

if  ((flag & FMODE_WRITE) && !(flag & O_APPEND))

return -EPERM;

if (flag & O_TRUNC)

return -EPERM;

}

/* O_NOATIME can only be set by the owner or superuser */

//如果操作带有O_NOATIME标志,则只允许文件的所有者或者是root用户操作

if (flag & O_NOATIME)

if (current->fsuid != inode->i_uid && !capable(CAP_FOWNER))

return -EPERM;

/*

* Ensure there are no outstanding leases on the file.

*/

error = break_lease(inode, flag);

if (error)

return error;

if (flag & O_TRUNC) {

error = get_write_access(inode);

if (error)

return error;

/*

* Refuse to truncate files with mandatory locks held on them.

*/

//检查文件系统是否使用了强制锁且已经加上了强制锁

error = locks_verify_locked(inode);

if (!error) {

DQUOT_INIT(inode);

//对文件进行截尾

error = do_truncate(dentry, 0);

}

put_write_access(inode);

if (error)

return error;

} else

if (flag & FMODE_WRITE)

DQUOT_INIT(inode);

return 0;

}

在这里,涉及到了两种锁.文件租借锁与强制锁.简单介绍如下:

文件租借锁:

当一个进程试图打开被租借锁保护的文件时,它会阻塞.同时,拥有这个租借锁的所有进程都会收到一个相应的信号.拥有进程会更新文件的内容,使文件保持一致.如果拥有租借锁的进程没有在规定时间内完成.则内核将租借锁删除,因租借锁阻塞的时候进程继续执行.

强制锁:

系统默认是劝告锁,当挂载文件系统时指定MS_MANDLOCK安装标志时,强制锁被打开.文件的组设置位为1且组执行位为0的进程都是强制锁的候选者.

break_lease()用来判断文件是否有租借锁.被对租借锁的相应处理.代码如下:

static inline int break_lease(struct inode *inode, unsigned int mode)

{

//当前节点有锁

if (inode->i_flock)

return __break_lease(inode, mode);

//没有锁直接返回

return 0;

}

int __break_lease(struct inode *inode, unsigned int mode)

{

int error = 0, future;

struct file_lock *new_fl, *flock;

struct file_lock *fl;

int alloc_err;

unsigned long break_time;

int i_have_this_lease = 0;

//申请一个租借锁

alloc_err = lease_alloc(NULL, mode & FMODE_WRITE ? F_WRLCK : F_RDLCK,

&new_fl);

lock_kernel();

//对文件中现有租借锁的延时进行处理

time_out_leases(inode);

flock = inode->i_flock;

//如果没有锁,或者锁不为租借锁,退出

//租借锁都会存放在inode->i_flock的头部

if ((flock == NULL) || !IS_LEASE(flock))

goto out;

//如果进程本身是租借锁的拥有者,i_have_this_lease为1

for (fl = flock; fl && IS_LEASE(fl); fl = fl->fl_next)

if (fl->fl_owner == current->files)

i_have_this_lease = 1;

if (mode & FMODE_WRITE) {

/* If we want write access, we have to revoke any lease. */

//如果是带有写属性的open`需要将租借锁全部都移除

future = F_UNLCK | F_INPROGRESS;

} else if (flock->fl_type & F_INPROGRESS) {

/* If the lease is already being broken, we just leave it */

//操作正在进行

future = flock->fl_type;

} else if (flock->fl_type & F_WRLCK) {

/* Downgrade the exclusive lease to a read-only lease. */

future = F_RDLCK | F_INPROGRESS;

} else {

/* the existing lease was read-only, so we can read too. */

goto out;

}

//如果分配内存失败且本进程不允许强制锁且不允许阻塞.退出

if (alloc_err && !i_have_this_lease && ((mode & O_NONBLOCK) == 0)) {

error = alloc_err;

goto out;

}

//设置break_time

break_time = 0;

if (lease_break_time > 0) {

break_time = jiffies + lease_break_time * HZ;

if (break_time == 0)

break_time++; /* so that 0 means no break time */

}

//因为进程要获得此租用锁了,将其类型更将,指定延时到达时间为初始化时间

//且向其它拥有租用锁的进程发送信号

for (fl = flock; fl && IS_LEASE(fl); fl = fl->fl_next) {

if (fl->fl_type != future) {

fl->fl_type = future;

fl->fl_break_time = break_time;

kill_fasync(&fl->fl_fasync, SIGIO, POLL_MSG);

}

}

//如果进程本身就是锁的拥有者,或者不允许阻塞,退出

if (i_have_this_lease || (mode & O_NONBLOCK)) {

error = -EWOULDBLOCK;

goto out;

}

restart:

//计算剩余的延时到达时间

break_time = flock->fl_break_time;

if (break_time != 0) {

break_time -= jiffies;

if (break_time == 0)

break_time++;

}

//将新分配的租用锁插入到链表。直接break time到达,或者是被其它拥有者唤醒

error = locks_block_on_timeout(flock, new_fl, break_time);

if (error >= 0) {

//如果正常返回,更新结点中的租借锁状态

if (error == 0)

time_out_leases(inode);

/* Wait for the next lease that has not been broken yet */

//如果还有租用锁没有被处理,继续前述的处理过程

for (flock = inode->i_flock; flock && IS_LEASE(flock);

flock = flock->fl_next) {

if (flock->fl_type & F_INPROGRESS)

goto restart;

}

error = 0;

}

out:

unlock_kernel();

if (!alloc_err)

locks_free_lock(new_fl);

return error;

}

对强制锁的检查是在locks_verify_locked()中完成的.代码如下:

static inline int locks_verify_locked(struct inode *inode)

{

//强制锁的初始条件

//即:1:挂载文件系统的类型为MS_MANDLOCK 且文件的组设置位为1且组执行位为0

if (MANDATORY_LOCK(inode))

//判断文件中是否有强制锁

return locks_mandatory_locked(inode);

return 0;

}

int locks_mandatory_locked(struct inode *inode)

{

fl_owner_t owner = current->files;

struct file_lock *fl;

/*

* Search the lock list for this inode for any POSIX locks.

*/

lock_kernel();

for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {

//判断是否为强制锁

if (!IS_POSIX(fl))

continue;

//不是进程的强制锁.说明被其它的进程置了强制锁了

if (fl->fl_owner != owner)

break;

}

unlock_kernel();

return fl ? -EAGAIN : 0;

}

另外,还有一个很重要的过程,即对文件截短的操作.因为这个过程涉及到i_mapping的东东.以后再专题分析.

回到filp_open().找到文件对应的结点之后,要将inode结构与file结构关联起来.这里在dentry_open()中处理的.它的代码如下:

struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags)

{

struct file * f;

struct inode *inode;

int error;

error = -ENFILE;

f = get_empty_filp();

if (!f)

goto cleanup_dentry;

f->f_flags = flags;

f->f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;

inode = dentry->d_inode;

if (f->f_mode & FMODE_WRITE) {

error = get_write_access(inode);

if (error)

goto cleanup_file;

}

f->f_mapping = inode->i_mapping;

//file所对应的dentry与vfsmount

f->f_dentry = dentry;

f->f_vfsmnt = mnt;

f->f_pos = 0;

//将文件的操作指向inode->i_fop

f->f_op = fops_get(inode->i_fop);

file_move(f, &inode->i_sb->s_files);

//如果file结构中指定了文件的open函数,调用它

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);

file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);

/* NB: we're sure to have correct a_ops only after f_op->open */

if (f->f_flags & O_DIRECT) {

if (!f->f_mapping->a_ops || !f->f_mapping->a_ops->direct_IO) {

fput(f);

f = ERR_PTR(-EINVAL);

}

}

return f;

cleanup_all:

fops_put(f->f_op);

if (f->f_mode & FMODE_WRITE)

put_write_access(inode);

file_kill(f);

f->f_dentry = NULL;

f->f_vfsmnt = NULL;

cleanup_file:

put_filp(f);

cleanup_dentry:

dput(dentry);

mntput(mnt);

return ERR_PTR(error);

}

从上面的代码中可以看出.对file的各种操作,都会对应到inode的f_op中.

在上面的代码曾分析到,对不存在的文件会调用vfs_create().继续会调用目录结点的create()方法.下面分析一下rootfs和ext2中的create实现.

2.1: rootfs中的文件创建

经过以前的分析,可得知rootfs中inode对应的操作如下:

static struct inode_operations ramfs_dir_inode_operations = {

.create       = ramfs_create,

.lookup       = simple_lookup,

.link         = simple_link,

.unlink       = simple_unlink,

.symlink = ramfs_symlink,

.mkdir        = ramfs_mkdir,

.rmdir        = simple_rmdir,

.mknod        = ramfs_mknod,

.rename       = simple_rename,

}

对应的create为ramfs_create.代码如下:

static int ramfs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)

{

//S_IFREG模式

return ramfs_mknod(dir, dentry, mode | S_IFREG, 0);

}

从上面可以看到.上面的过程与rootfs中目录的建立大体相同.只是文件的模式改为了S_IFREG.即一般的文件.

2.2:ext2中的文件创建

经过前面的分析我们可以得知,ext2中目录对应的操作为:

struct inode_operations ext2_dir_inode_operations = {

.create       = ext2_create,

.lookup       = ext2_lookup,

.link         = ext2_link,

.unlink       = ext2_unlink,

.symlink = ext2_symlink,

.mkdir        = ext2_mkdir,

.rmdir        = ext2_rmdir,

.mknod        = ext2_mknod,

.rename       = ext2_rename,

#ifdef CONFIG_EXT2_FS_XATTR

.setxattr = generic_setxattr,

.getxattr = generic_getxattr,

.listxattr    = ext2_listxattr,

.removexattr  = generic_removexattr,

#endif

.setattr = ext2_setattr,

.permission   = ext2_permission,

}

其create函数的入口为ext2_create().代码如下:

static int ext2_create (struct inode * dir, struct dentry * dentry, int mode, struct nameidata *nd)

{

//分配一个新的结点

struct inode * inode = ext2_new_inode (dir, mode);

int err = PTR_ERR(inode);

//指定i_op和i_fop.页面缓存的操作方式

if (!IS_ERR(inode)) {

inode->i_op = &ext2_file_inode_operations;

inode->i_fop = &ext2_file_operations;

if (test_opt(inode->i_sb, NOBH))

inode->i_mapping->a_ops = &ext2_nobh_aops;

else

inode->i_mapping->a_ops = &ext2_aops;

//将inode置脏

mark_inode_dirty(inode);

err = ext2_add_nondir(dentry, inode);

}

return err;

}

ext2_new_inode()的代码在前面的分析中已经讨论过.这里不再赘述.

三:文件的关闭

关闭文件在用户空间的api接口为close().它在内核中的系统调用入口是sys_close().代码如下:

asmlinkage long sys_close(unsigned int fd)

{

struct file * filp;

struct files_struct *files = current->files;

spin_lock(&files->file_lock);

//参数有效性判断

if (fd >= files->max_fds)

goto out_unlock;

//取得文件描述符对应的file

filp = files->fd[fd];

if (!filp)

goto out_unlock;

//将文件描述符对应的file置空

files->fd[fd] = NULL;

//清除close_on_exec的标志位,表示进程结束时不应该关闭对应位的文件描述对象

FD_CLR(fd, files->close_on_exec);

//清除文件描述的分配位图

__put_unused_fd(files, fd);

spin_unlock(&files->file_lock);

return filp_close(filp, files);

out_unlock:

spin_unlock(&files->file_lock);

return -EBADF;

}

转到filp_close():

int filp_close(struct file *filp, fl_owner_t id)

{

int retval;

/* Report and clear outstanding errors */

retval = filp->f_error;

if (retval)

filp->f_error = 0;

//file引用计数为零.已经无效了

if (!file_count(filp)) {

printk(KERN_ERR "VFS: Close: file count is 0\n");

return retval;

}

//如果文件对象有flush()操作,调用之

if (filp->f_op && filp->f_op->flush) {

int err = filp->f_op->flush(filp);

if (!retval)

retval = err;

}

//发出flush通告

dnotify_flush(filp, id);

//文件要关闭了,将进程拥有的文件的强制锁清除掉

locks_remove_posix(filp, id);

//释放file对象

fput(filp);

return retval;

}

下面以具体的文件为例,讨论file的flush过程.

3.1 rootfs的flush()

Rootfs格式的一般文件的i_fop对应为:

struct file_operations ramfs_file_operations = {

.read         = generic_file_read,

.write        = generic_file_write,

.mmap         = generic_file_mmap,

.fsync        = simple_sync_file,

.sendfile = generic_file_sendfile,

.llseek       = generic_file_llseek,

}

可以看到里面并没有flush()操作,对文件的关闭无需进行特殊的操作.

3.2:ext2的flush()

Ext2类型的文件系统对应的普通文件的i_fop为:

struct file_operations ext2_file_operations = {

.llseek       = generic_file_llseek,

.read         = generic_file_read,

.write        = generic_file_write,

.aio_read = generic_file_aio_read,

.aio_write    = generic_file_aio_write,

.ioctl        = ext2_ioctl,

.mmap         = generic_file_mmap,

.open         = generic_file_open,

.release = ext2_release_file,

.fsync        = ext2_sync_file,

.readv        = generic_file_readv,

.writev       = generic_file_writev,

.sendfile = generic_file_sendfile,

}

可以看到,里面也没有定义flush操作.

四:小结

在本节里,主要概述了文件的打开与关闭操作.其中文件的关闭操作对大部份文件系统来说,只要处理好进程本身的文件描述符映射就可以了.无需进程其它特殊的操作.

linux文件系统打开不了,linux文件系统之文件的打开与关闭相关推荐

  1. 后缀名.dat文件在选择打开方式时误勾选了用记事本打开, 结果电脑上的*.dat文件的打开方式都变成了记事本,没有修改文件的内容,如何恢复.bat文件的默认打开方式

    项目场景: 后缀名.dat文件在选择打开方式时误勾选了用记事本打开, 结果电脑上的*.dat文件的打开方式都变成了记事本,没有修改文件的内容 问题描述 后缀名.dat文件在选择打开方式时误勾选了用记事 ...

  2. 怎么修复html文件打开方式,如何修复快捷方式lnk文件的打开方式

    正常情况下,在桌面上会看到很多快捷方式,而*.lnk是快捷方式的后缀,一般情况下,快捷方式是不会显示后缀的,如果显示了后缀,肯定是你的电脑有问题了(见下图) lnk文件是用于指向其他文件(如,程序)的 ...

  3. iphone6php怎么打开,苹果手机中的heic格式文件怎么打开

    苹果手机中的heic格式文件怎么打开?在使用手机拍照时经常会显示图片是heic格式文件,这样会导致图片不仅在手机上打不开,在电脑上也是如此.那么该如何解决呢?下面这篇教程我们就来为大家分享一下,苹果手 ...

  4. html 打开方式6,教你aspx文件的打开方式

    ASP.NET 是建立在公共语言运行库上的编程框架,可用于在服务器上生成功能强大的 Web 应用程序,通过IIS解析执行后可以得到动态页面,是微软推出的一种新的网络编程方法,而不是asp的简单升级,那 ...

  5. 压缩包里面是html文件怎么打开方式,展示电脑rar压缩包文件怎么打开?教你正确打开方式...

    不管是生活还是工作需要,经常需要把文件打包处理,这样可以避免在发送文件的时候出现丢失或损坏文件,那么总会收到rar格式的文件,如何打开rar格式的文件呢?今天跟小编一起看下打开rar格式文件的详细操作 ...

  6. linux怎么导出dmp文件怎么打开,Linux开发实用工具之详解coredump-dmp文件怎么打开...

    在开发Linux的程序时,例如游戏服务器,有时会异常终止或者崩溃,此时没有任何日志,也不能定位到崩溃代码的位置,有些老手会告诉你去查看coredump,那什么是coredump, 又怎么查看呢?这里就 ...

  7. linux桌面怎么解压tar文件,Linux中tar解压缩命令详解-tar文件怎么打开

    语法:tar [主选项+辅选项] 文件或者目录 使用该命令时,主选项是必须要有的,它告诉tar要做什么事情,辅选项是辅助使用的,可以选用. 主选项:c 创建新的档案文件.如果用户想备份一个目录或是一些 ...

  8. arctime工程文件怎么打开_微信dat后缀的文件怎么打开

    电脑微信image 下的dat文件都是图片进行加密后的文件.需要将dat文件解密回图片才可以查看. 原理:将图片的字节通过加密值进行异或,然后将异或后的数据重新存储到dat文件中.所以,如果解密的话, ...

  9. .ps文件怎么打开_ps界面的认识及文件的打开

    ps处理的图像叫做位图,颜色值非常丰富,点阵图.像素是正方形的,一个像素只能有一个颜色. 认识ps ps界面介绍 ps界面介绍 上面我们叫菜单栏 左侧,工具栏,箭头我们称为单双链.每一个工具右下角有实 ...

  10. Linux下命令行打开word、excel、pdf文件

    Linux下命令行打开word.excel.pdf文件 1.打开word文件 wps xxx.docx xdg-open xxx.docx 2.打开excel文件 et xxx.xlsx xdg-op ...

最新文章

  1. C语言实验——矩阵转置_JAVA
  2. 基础知识—表达式与语句-表达式
  3. Spark基础学习笔记22:Spark RDD案例分析
  4. matplotlib mysql_matplotlib简介
  5. 均方根误差不超过_快速平方根倒数算法
  6. CSS超全笔记(适合新手入门)
  7. Flutter布局锦囊---屏幕顶部提醒
  8. ffmpeg命令_温故知新:ffmpeg操作《天空之城》。窗口党勿入,都是指令!
  9. 北京公司买车,都需要什么手续?摇号有什么特殊要求?
  10. Java 断点续传以及在线视频播放可选择任一时间播放原理
  11. [转]netstat -tnl 列出监听中的连接,查看端口是否开启
  12. Codeforces 1153C
  13. 为什么RSA 公钥指数(e=65537)
  14. 元素定位163邮箱账号密码输入框问题,iframe嵌套
  15. 公关文秘专业要学计算机,2017年公关文秘专业大学排名
  16. 如何理解服务注册和服务发现
  17. 使用Scratch 3.0制作弹球游戏(一)——课题准备
  18. 20175227张雪莹 2018-2019-2 《Java程序设计》第五周学习总结
  19. 《Head First 设计模式》读书笔记——组合模式
  20. Docker基础03--Dockerfile详解与镜像发布--(狂神说docker学习笔记)

热门文章

  1. mysql(版本5.7)(操作数据库 操作表结构 操作表记录)
  2. Firefox 设置
  3. SV学习笔记—覆盖率类型
  4. 重拾C#教程:正则表达式
  5. WebGIS第五课:地图控件的添加
  6. 如何将PDF转换成CAD文件?简单快捷的方法分享给你
  7. 扫频信号设置 matlab,如何利用usrp2和matlab2013a结合,设计一个扫频信号发生器?...
  8. 数项级数的狄利克雷判别法
  9. python中执行py文件出错(提示File “<stdin>”,line 1,SyntaxError:invalid syntax)
  10. C++各大名库的介绍