-
open系统调用源码剖析
2020-02-24 18:25:22如题,open系统调用,linux内核设计有意思,想来应该是打开文件返回文件描述符,当read或者write事应该会触发缺页中断,这有和内存管理挂上勾了,所以说内核设计者是真的强。1.简介
需要前备知识,对教程的task_struct结构体有一定的了解,尤其是要对其中的struct file有一定了解,明白物理内存与虚拟内存的映射关系,明白文件系统的分层,最最重要的一点是会使用工具追偿源码,现在就来看一看open系统调用。
#define AT_FDCWD -100 /* Special value used to indicate openat should use the current working directory. */ //linux-4.13.16\fs\open.c SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) { ... return do_sys_open(AT_FDCWD, filename, flags, mode); }
略去一些细节性的东西(话说看内核源码,最忌讳深入追求细节),AT_FDCWD根据注释应该是指示在当前文件夹下查找吧,接下来调用do_sys_open函数:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) { .../*相关数据结构定义与参数检查*/ fd = get_unused_fd_flags(flags);//不论我文件存在与否,都要一个fd绑定struct file if (fd >= 0) { struct file *f = do_filp_open(dfd, tmp, &op);//依据tmp结构获取文件的struct file if (IS_ERR(f)) {//失败了走这儿。。。 put_unused_fd(fd); fd = PTR_ERR(f); } else {//找到了走这儿。。。 fsnotify_open(f); fd_install(fd, f); } } putname(tmp); return fd;//为open函数返回的fd }
关于get_unused_fd_flags获取空闲fd,其实不算太复杂,先找到进程对应的struct file结构体数组,找到还没有被使用的即可,而且剖析源码会发现,使用了好多自旋锁,原因无他,当前进程不想被阻塞。所以主要看do_filp_open看它怎样找到一个struct file结构。
2.敢问我的file结构体在何方
首先认识一个关于保存文件路径等相关信息用于起辅助作用的结构体:
//linux-4.13.16\fs\namei.c struct nameidata { struct path path; //该结构体标识了文件路径相关的信息 struct qstr last; struct path root; struct inode *inode; /* path.dentry.d_inode */ unsigned int flags; unsigned seq, m_seq; int last_type; unsigned depth; int total_link_count; struct saved { struct path link; struct delayed_call done; const char *name; unsigned seq; } *stack, internal[EMBEDDED_LEVELS]; struct filename *name; struct nameidata *saved; struct inode *link_inode; unsigned root_seq; int dfd; }; //其初始化方式 static void set_nameidata(struct nameidata *p, int dfd, struct filename *name) { struct nameidata *old = current->nameidata; p->stack = p->internal; p->dfd = dfd; p->name = name; p->total_link_count = old ? old->total_link_count : 0; p->saved = old; current->nameidata = p;//为何要挂在进程上? }
do_filp_open:
struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op) { struct nameidata nd; int flags = op->lookup_flags; struct file *filp; set_nameidata(&nd, dfd, pathname);//初始化了nd变量,将路径名等信息均保存在该结构 filp = path_openat(&nd, op, flags | LOOKUP_RCU);//加载文件至内存,返回struct file ...//失败了怎么处理 restore_nameidata(); return filp; }
其实到此处就应该明白一点,任何东西都不是大白菜,平时文件在磁盘上,想要操作它显然直接从磁盘读取是不合适的,内核设计的文件系统,内存关联设计原理告诉我们它应该会将文件先映射在内存里面,然后初始化我们分配好的struct file 结构体,这上面会有丰富的信息包括文件的inode节点等。故现在我们就来看一看它是如何串联的?
static struct file *path_openat(struct nameidata *nd, const struct open_flags *op, unsigned flags) { const char *s; struct file *file; int opened = 0; int error; file = get_empty_filp(); ... file->f_flags = op->open_flag;//初始化f_flags ... s = path_init(nd, flags);//继续初始化struct nameidata结构,返回输入的路径名 ...//下面link_path_walk进行路径名的划分 while (!(error = link_path_walk(s, nd)) && (error = do_last(nd, file, op, &opened)) > 0) {...} //先解析路径前面的部分,然后调用do_last解析路径最后面的部分 terminate_walk(nd); 。。。 return file; }
do_last分两种情况解析处理,若在文件路径上找到了要查找的文件的对应的dentry项则返回,否则就调用相关函数在kmem_cache里面分配一个dentry对象:
static int do_last(struct nameidata *nd, struct file *file, const struct open_flags *op, int *opened){ ... error = lookup_fast(nd, &path, &inode, &seq);//在缓存中找dentry ... error = lookup_open(nd, &path, file, op, got_write, opened); //此函数处理dentry没有找到的情况 //先分配dentry,然后调用inode所在文件系统的lookip_open函数在文件系统中查找dentry //查找到就好,若是仍旧没有查找到,在通过看flag是否有O_CREAT标记确定是否是创建文件操作 //是则创建之,否则返回错误 error = vfs_open(&nd->path, file, current_cred()); //这处最后调用vfs_open真正的打开文件 }
3.总结
总的来说,这个open系统调用是比较复杂的。
- 通过task_struct 找到一个空闲文件描述符
- 根据输入的路径名在dentry对应的高速缓存中找到要打开文件对应的dentry数据结构
- 若没有找到则调用相关函数在dentry对应的kmem_cache中创建新的dentry对象,然后去fs查找
- 根据dentry结构找到对应的inode对象用来初始化file结构体
- 最后调用vfs_open函数真正打开文件
附注
- 硬盘文件系统由一个个的inode块和普通数据块组成
- inode结构我们又将它叫做元数据,保存文件好多类型的信息以及具体的数据块
- ext2与ext3文件系统定义一个大小为15的数组,前面12项直接保存具体的块,后面3项依次为1,2,3级索引。
- ext4引入了一种叫做extens的数据结构组织形式,它可以放置一个extent_header与4个普通的索引或者叶子节点exten,这样就组成了一个多叉树,扩展性大大提高,文件的容量也大大提高
- 我们使用inode位图与块位图来组织和管理磁盘上的空闲块。
- 这样我们就可以串起来了,调用open函数,若确实有这个文件就直接打开就好,若没有且设置了O_CREAT标记,就调用表示这个文件的dentry的inode的create函数创建之,在创建的时候其实就是调用文件系统的相关函数,从位图中找到空闲的inode块
- 受限于块位图与大量的块以及inode位图与inode块索引的磁盘容量太小的缘故,我们将这样一个组合叫做一个块组,这样一个个块组就构成了我们的文件系统。
- 对于文件系统中的每一个块组我们有一个超级快,记录了一些相关信息,包括此块组有多少块多少inode等。。。
- 软硬链接的根本区别就是对inode的使用方式不同,硬链接与文件共享inode而软连接有自己的inode只不过其数据块里面指向的是真正的文件而已。最后,软链接可以跨文件系统,而硬链接不可以。
-
浅析linux中open系统调用
2014-05-18 10:29:27浅析linux中open系统调用 从2.6.19的linux内核开始,内核的系统调用使用函数syscall,其函数原型为:int syscall(int number, ...)其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。以x86...浅析linux中open系统调用
从2.6.19的linux内核开始,内核的系统调用使用函数syscall,其函数原型为:int syscall(int number, ...)其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。以x86平台为例,系统调用号在内核源码中的路径是/arch/x86/include/asm/unistd_32.h头文件中定义。其中大部分以__NR_开头,比如open的系统调用号是5。
本文以字符设备的驱动为例,分析系统调用的执行过程,内核版本为2.6.35.
1、用户空间到内核的转换
系统调用需要一个从用户空间到内核空间的转换,不同平台转换的指令不同,这种特定的指令称作操作系统的陷入(operating system trap)。X86结构中使用软中断x080来实现。即汇编指令 int $0x80.通过软中断0x80 ,系统就会跳到一个预设的内核空间地址。它指向系统调用处理程序system_call,在arch/x86/kernel/entry_32.S 中以汇编语言编写,该过程主要有2个步骤。
(1)系统启动时,对INT 0x80进行一定的初始化。
使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int ,如下图所示。
(2)设置中断描述符表
start_kernel函数中(init/main.c)调用trap_init()(arch/x86/kernel/trap.c)函数,设置中断描述符表。在trap_init()该函数里,实际上是通过调用函数set_system_trap_gate(SYSCALL_VECTOR, &system_call);来完成该项的设置的。其中的SYSCALL_VECTOR就是0x80,而system_call则是一个汇编子函数,它即是中断0x80的处理函数,主要完成两项工作:寄存器上下文的保存、跳转到系统调用处理函数。
2、系统调用函数的入口
syscall_call函数到系统调用服务例程:在上面执行软终端0x80时,系统调用号会被放入eax寄存器,system_call()函数读取eax寄存器获取当前系统调用的调用号。然后将其乘以4生成偏移地址,然后再以sys_call_table为基址。基址+偏移地址=>系统调用服务例程的地址。其中sys_call_table基址在文件arch/x86/kernel/syscall_table_32.S中定义。同时table表中每一项例程的地址占用4个字节,所以上面乘以4。
到这儿system_call()就到服务例程的地址了。然后另一个问题-参数传递需要解决。由于系统调用例程在定义时时用 asmlinkage 标记了的,所以编译器仅从堆栈中获取该函数的参数。在进入system_call函数前,用户应用会把参数存放到寄存器中,system_call函数执行时会首先把这些寄存器压入堆栈。这样对系统调用服务例程可以直接从堆栈照片能够获取参数。
3、系统调用函数的执行
open会最终执行sys_open函数:
里面调用do_sys_open()函数:
在上图的L884行中,获取未被使用的文件描述符,在do_filp_open()函数中打开文件。其函数调用过程如下:
sys_open -> do_sys_open -> do_filp_open ->do_last-> nameidata_to_filp -> __dentry_open
4、驱动代码file_operations的调用
在 __dentry_open函数中:
L685中 open = f->f_op->open;即调用chrdev_open。
在file: fs/char_dev.c中static int chrdev_open(struct inode *inode, struct file *filp)调用ret = filp->f_op->open(inode,filp);即为真正的file_operations中的open函数。
当然,在VFS层中对open函数的操作远不止上文描述的这么简单,会进行权限和打开方式的判断等。普通的驱动开发者很少涉及对这部分代码的修改,主要还是学习和欣赏Linux内核。
-
open系统调用的源码
2010-02-21 10:30:00用户界面我们使用系统调用open来打开一个文件,例如:fd = open( "/home/mine/data/myfile",O_RDWR...下面来看看Linux是如何完成的,首先是系统调用的代码:sys_open的源程序asmlinkage long sys_open(const char * filen用户界面
我们使用系统调用open来打开一个文件,例如:
fd = open( "/home/mine/data/myfile",O_RDWR|O_CREAT);
下面来看看Linux是如何完成的,首先是系统调用的代码:
sys_open的源程序
asmlinkage long sys_open(const char * filename, int flags, int mode)
{
char * tmp;
int fd, error;
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
tmp = getname(filename);
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd();/*<-----------------------------------(1) */
if (fd >= 0) {
struct file *f = filp_open(tmp, flags, mode);/*<---(2) */
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
fd_install(fd, f);/*<------------------------------(3) */
}
out:
putname(tmp);
}
return fd;
out_error:
put_unused_fd(fd);
fd = error;
goto out;
}
这里面完成的几个工作
1.注意到返回一个整数fd,所以在(1)的位置获得一个整数fd。
2.根据路径/home/mine/data/myfile找到(或创建)文件,并且创建一个结构file(见(2))。
3.将整数fd和file结构指针f联系起来(见(3))。
几个问题
1.在《外设中的目录项(以EXT2为例)和内存中的"目录项"的比较
》一文中可见,一个文件是和inode,进而和dentry联系,那么为什么要创建一个结构file?
原因是,我们可以有多个对同一个文件操作的"会话",例如一个文件描述符fd1用于读,另一个fd2�
糜谛础d1的位置是文件开头,fd2的位置是文件结尾等等。因此,file的目的是记录一次对文件�
僮鞯幕峄啊�
2.为什么不直接返回结构file指针,而要用一个描述符整数fd?
原因返回的信息是被用户程序使用的,不可能返回结构指针。所以采用间接的方法,返回一个整数
,这个整数对应一个file的指针。
下面讨论的专题
1.Linux对文件描述符,结构file的有关实现。
2.如何从路径找到(或创建)文件。
我们的
href=http://www2.linuxforum.net/ker_plan/index/main.htm>内核文档与源码学习lucian_yao
(addict)
01-04-30 14:35
文件描述符与file结构
[re: lucian_yao ]
一个任务(task)的描述结构task_struct中的字段files指向数据结构 files_struct,这个结构用于管理该任务的文件。
在files_struct中,
fd_array 就是联系描述符fd和文件结构file指针的数组,事实上,下标为fd的元素中保存了指向file结构的指针。
open_fds 是一个标记打开文件的位图。例如,如果fd=7被已被使用,那么open_fds的bit 7就为1。这个结构用于快速查找未使用的描述符。
close_on_exec 是一个标记位图。某个bit为1表明在这个任务执行系统调用execve的时候,这个bit对应的文件将被关闭,这个描述符将空出来。
fd_array中包含的指向结构file的指针,如前所述,每个file描述和一个文件的会话,因此,它(通过dentry)指向文件结构inode,另外,包含了会话信息。
如图,两个file结构指向同一个dentry,从而指向同一个inode,但是各自保留了私有的会话信息,如偏移量f_op等。
我们的
内核文档与源码学习lucian_yao
(addict)
01-05-08 11:02
files_struct这三个数组的初始化、再分配
[re: lucian_yao ]
files_struct这三个数组的初始化、再分配
初始化
files_struct内置了3个初始的数组:
--------------------------------
struct files_struct {
... ...
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;/*<----------------------这里*/
fd_set open_fds_init;/*<---------------------------这里*/
struct file * fd_array[NR_OPEN_DEFAULT];/*<--------这里*/
};
---------------------------------
其中,fd_set为:
---------------------
typedef __kernel_fd_set fd_set;
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
---------------------
1024/8=128个字节长。
因此,当执行fork,在建立新的任务的文件结构时(copy_files),执行下面的语句:
-----------------------------------
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{ ... ...
newf = kmem_cache_alloc(files_cachep,
SLAB_KERNEL);/*<-------------分配files_struct,自然分配了内嵌的数组*/
... ...
newf->close_on_exec = &newf->close_on_exec_init;/*<----------------指向内部的数组*/
newf->open_fds = &newf->open_fds_init;
newf->fd = &newf->fd_array[0];
}
------------------------------------
也就自然地分配了上面3个数组,然后将files_struct的各个指针指向内部的这3个数组。
再分配
但是,上面初始分配的数组不一定够用,Linux采用的策略是,重新分配这3个数组(从而可以保证�
嵌际橇�),然后将老的数组中的内容拷贝到新的数组中,再释放老的数组。
类似remalloc的方法。
下面举expand_fdset的例子(描述符数组的再分配是由expand_fd_array完成的):
----------------------------------------------------------
/*
* Expand the fdset in the files_struct. Called with the files spinlock
* held for write.
*/
int expand_fdset(struct files_struct *files, int
nr)/*<----------------nr是要分配的大小*/
{
fd_set *new_openset = 0, *new_execset = 0;
int error, nfds = 0;
error = -EMFILE;
if (files->max_fdset >= NR_OPEN || nr >= NR_OPEN) /*<-----------控制数组的上界*/
goto out;
nfds = files->max_fdset;
write_unlock(&files->file_lock);
/* Expand to the max in easy steps */
do
{/*<---------------------------------获得数组的大小,不一定等于nr,如果比一页小,则增长
到一页,不然以2的幂增长*/
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);
write_lock(&files->file_lock);
if (!new_openset || !new_execset)
goto out;
error = 0;
/* Copy the existing tables and install the new pointers */
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.
*/
if (i) {
memcpy (new_openset, files->open_fds,
files->max_fdset/8);/*<---将老数组拷贝到新数组里,多余部分置0*/
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);/*<-------------------更换files_struct中的大小和指针*/
new_openset = xchg(&files->open_fds, new_openset);
new_execset = xchg(&files->close_on_exec, new_execset);
write_unlock(&files->file_lock);
free_fdset (new_openset, nfds);/*<-------------------------释放老的数组*/
free_fdset (new_execset, nfds);
write_lock(&files->file_lock);
return 0;
}
/* Somebody expanded the array while we slept ... */
out:
write_unlock(&files->file_lock);
if (new_openset)
free_fdset(new_openset, nfds);
if (new_execset)
free_fdset(new_execset, nfds);
write_lock(&files->file_lock);
return error;
}
--------------------------------------------------------------
注意,释放老的数组时,如果是原来嵌在files_struct中的,则不能释放:
------------------------------------------
void free_fdset(fd_set *array, int num)
{
int size = num / 8;
if (!array) {
printk (KERN_ERR __FUNCTION__ "array = 0 (num = %d)/n", num);
return;
}
if (num <= __FD_SETSIZE) /* Don't free an embedded fdset
*//*<-------内嵌的不能释放!*/
return;
else if (size <= PAGE_SIZE)
kfree(array);
else
vfree(array);
}
---------------------------------------
我们的
href=http://www2.linuxforum.net/ker_plan/index/main.htm>内核文档与源码学习lucian_yao
(addict)
01-05-08 11:04
如何获得空闲的fd
[re: lucian_yao ]
现在回到sys_open中,看看是如何获得一个未使用的文件描述符的。
下面的函数获得一个未使用的文件描述符。
--------------------------------------------------------------
/*
* Find an empty file descriptor entry, and mark it busy.
*/
int get_unused_fd(void)
{
struct files_struct * files = current->files;
int fd, error;
error = -EMFILE;
write_lock(&files->file_lock);
repeat:
fd = find_next_zero_bit(files->open_fds,
files->max_fdset,
files->next_fd); /*<--------从原有位图中找一个bit为0,这样它对应的描述符为空*/
/*
* 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 >= 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;
}
FD_SET(fd, files->open_fds);/*<----------------------将该描述符对应位图的bit置1*/
FD_CLR(fd,
files->close_on_exec);/*<-----------------将该位清0,如果要置1,需调用fcntl*/
files->next_fd = fd + 1;/*<--------------------------下次查找从下一个bit开始*/
#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:
write_unlock(&files->file_lock);
return error;
}
--------------------------------------------------------------------------------
在sys_open中,当获得空的描述符fd以及一个file结构指针f后,调用
fd_install(fd, f)
将描述符数组下标为fd的元素置为指针f:
---------------------------------------------------
static inline void fd_install(unsigned int fd, struct file * file)
{
struct files_struct *files = current->files;
write_lock(&files->file_lock);
if (files->fd[fd])
BUG();
files->fd[fd] = file;/*<-------------------设置指针*/
write_unlock(&files->file_lock);
}
----------------------------------------------------
这个线索下面的帖子是讨论fork和execve的,关于sys_open的内容在系统调用open下。
我们的
href=http://www2.linuxforum.net/ker_plan/index/main.htm>内核文档与源码学习lucian_yao
(addict)
01-05-08 11:10
文件指针的复制
[re: lucian_yao ]
文件指针的复制
在fork(以及clone)中,有件非常重要的工作,就是复制文件指针,从而使得父子任务可以对相同的文件操作。
fork与clone是不同的。如图:
在clone中,复制了父任务的task_struct,这样父子指向同样的files_struct,以后无论父子谁打开/关闭文件,对另一方来说,也相当于有同样的操作。也许就是所谓在同一环境中吧。
而在fork中,又复制了files_struct,并且复制了files_struct指向的fd_array,这样两个fd_array相同的描述符都指向同样的文件。
与clone不同的是,在fork结束时,父子初始状态是一致的,但是以后父子可以有独立的打开/关闭文件的状态(以及close_on_exec状态),进而可以更换某个文件描述符对应文件指针使得同样的描述符对应不同的file结构。
我们的
内核文档与源码学习lucian_yao
(addict)
01-05-08 11:11
"文件指针的复制"的源码
[re: lucian_yao ]
"文件指针的复制"的源码
-------------------------------------------------------------------------
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;
int open_files, nfds, size, i, error = 0;
/*
* A background process may not have any files ...
*/
oldf = current->files;
if (!oldf)
goto out;
if (clone_flags & CLONE_FILES) {/*<-------------------------------如果是clone,则不需要复制数组*/
atomic_inc(&oldf->count);
goto out;
}
tsk->files = NULL;
error = -ENOMEM;
newf = kmem_cache_alloc(files_cachep, SLAB_KERNEL);/*<-------------分配一个新的files_struct*/
if (!newf)
goto out;
atomic_set(&newf->count, 1);
newf->file_lock = RW_LOCK_UNLOCKED;
newf->next_fd = 0;
newf->max_fds = NR_OPEN_DEFAULT;
newf->max_fdset = __FD_SETSIZE;
newf->close_on_exec = &newf->close_on_exec_init;/*<---------------首先指向内置的数组,但是由于父亲的数组可能已经比这个大了,所以可能还会扩展*/
newf->open_fds = &newf->open_fds_init;
newf->fd = &newf->fd_array[0];
/* We don't yet have the oldf readlock, but even if the old
fdset gets grown now, we'll only copy up to "size" fds */
size = oldf->max_fdset;
if (size > __FD_SETSIZE) {/*<---------------------------------------如果父任务的位图数组已经扩展过了,那么子任务的就不能用内置数组了,也要扩展*/
newf->max_fdset = 0;
write_lock(&newf->file_lock);
error = expand_fdset(newf, size-1);
write_unlock(&newf->file_lock);
if (error)
goto out_release;
}
read_lock(&oldf->file_lock);
open_files = count_open_files(oldf, size);
/*
* Check whether we need to allocate a larger fd array.
* Note: we're not a clone task, so the open count won't
* change.
*/
nfds = NR_OPEN_DEFAULT;
if (open_files > nfds) {/*<----------------------------------------同样,如果父亲的已经扩展过了,子任务的也要扩展*/
read_unlock(&oldf->file_lock);
newf->max_fds = 0;
write_lock(&newf->file_lock);
error = expand_fd_array(newf, open_files-1);/*<------------注意这里,并没有完全照搬父亲的描述符数组,可能有所缩减(由open_files决定)*/
write_unlock(&newf->file_lock);
if (error)
goto out_release;
nfds = newf->max_fds;
read_lock(&oldf->file_lock);
}
old_fds = oldf->fd;
new_fds = newf->fd;
memcpy(newf->open_fds->fds_bits, oldf->open_fds->fds_bits, open_files/8);/*<------------------复制位图数组*/
memcpy(newf->close_on_exec->fds_bits, oldf->close_on_exec->fds_bits, open_files/8);
for (i = open_files; i != 0; i--) {/*<-------------------------------------------------------复制描述符数组(不一 定完全复制,由open_files决定)*/
struct file *f = *old_fds++;
if (f)/*<----------------------------------------------------------------------------增加file结构的引用数*/
get_file(f);
*new_fds++ = f;
}
read_unlock(&oldf->file_lock);
/* compute the remainder to be cleared */
size = (newf->max_fds - open_files) * sizeof(struct file *);
/* This is long word aligned thus could use a optimized version */
memset(new_fds, 0, size);
if (newf->max_fdset > open_files) {
int left = (newf->max_fdset-open_files)/8;
int start = open_files / (8 * sizeof(unsigned long));
memset(&newf->open_fds->fds_bits[start], 0, left);
memset(&newf->close_on_exec->fds_bits[start], 0, left);
}
tsk->files = newf;
error = 0;
out:
return error;
out_release:
free_fdset (newf->close_on_exec, newf->max_fdset);
free_fdset (newf->open_fds, newf->max_fdset);
kmem_cache_free(files_cachep, newf);
goto out;
}
我们的
内核文档与源码学习lucian_yao
(addict)
01-05-08 11:14
close_on_exec的使用
[re: lucian_yao ]
看看close_on_exec标志是如何使execve关闭对应的文件的
do_execve 中调用 search_binary_handler,后者有下面的语句:
-------------------------------------
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
... ...
retval = fn(bprm, regs);
-------------------------------------
而fmt->load_binary在下面定义:
------------------------------------
struct linux_binfmt {
struct linux_binfmt * next;
struct module *module;
int (*load_binary)(struct linux_binprm *, struct pt_regs *
regs);/*<----------这里定义*/
int (*load_shlib)(struct file *);
int (*core_dump)(long signr, struct pt_regs * regs, struct file * file);
unsigned long min_coredump; /* minimal dump size */
};
static struct linux_binfmt elf_format = {
NULL, THIS_MODULE, /*defined here!*/load_elf_binary, load_elf_library, elf_core_dump,
ELF_EXEC_PAGESIZE
};
------------------------------------
load_elf_binary 调用 flush_old_exec,后者调用 flush_old_files(current->files);
flush_old_files做的事情是:
把那些对应标志为1的文件关闭。
------------------------------------------------------
static inline void flush_old_files(struct files_struct * files)
{
long j = -1;
write_lock(&files->file_lock);
for (;;) {
unsigned long set, i;
j++;
i = j * __NFDBITS;
if (i >= files->max_fds || i >= files->max_fdset)
break;
set = files->close_on_exec->fds_bits[j];
if (!set)
continue;
files->close_on_exec->fds_bits[j] = 0;
write_unlock(&files->file_lock);
for ( ; set ; i++,set >>= 1) {
if (set & 1) {
/*这里!*/ sys_close(i);
}
}
write_lock(&files->file_lock);
}
write_unlock(&files->file_lock);
}
--------------------------------------------------------
我们的
href=http://www2.linuxforum.net/ker_plan/index/main.htm>内核文档与源码学习lucian_yao
(addict)
01-05-08 11:21
小结
[re: lucian_yao ]
在sys_open中,我们要完成几件事:
1.找到一个未使用的fd
2.根据open中提供的路径字符串找到(创建)对应的dentry(从而找到inode)
3.创建file结构,使得它指向这个dentry
4.在描述符数组中fd的位置设为指向file结构的指针
其中,1,4前面已经详细研究。2,3在sys_open中调用的filp_open中实现的。其中,
根据路径找dentry是由open_namei完成的
创建file结构是由dentry_open完成的
下面将分别详细研究open_namei,也就是"路径解析",以及创建file结构
我们的
内核文档与源码学习 -
syscall 系统调用陷入_linux 系统调用open 篇一
2020-12-22 18:42:32内核源码:linux-4.4 目标平台:ARM体系结构 源码工具:source insight 4说明: 文中由于 md 语法问题,无法在代码高亮的同时而忽略由于 __ 或者 * 造成斜体的 问题,所以类似 __user...open 对应的内核系统调用应用...内核源码:linux-4.4 目标平台:ARM体系结构 源码工具:source insight 4
说明: 文中由于 md 语法问题,无法在代码高亮的同时而忽略由于 __ 或者 * 造成斜体的 问题,所以类似 __user 改成 __ user,或者 char *filename 改成 char* filename。 通过在中间添加空格进行避免。注释统一使用了 \\。
open 对应的内核系统调用
应用层的 open 函数是 glibc 库封装了系统调用以比较友好的方式提供给开发者。 那么为什么要这么做? 这主要是从安全以及性能这两大方面进行了考虑:
在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用 户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植, 只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断, 从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号 ,比很多操作系统要少很多。
安全性与稳定性:内核驻留在受保护的地址空间,用户空间程序无法直接执行内核代码 ,也无法访问内核数据,通过系统调用
性能:Linux上下文切换时间很短,以及系统调用处理过程非常精简,内核优化得好,所以性能上 往往比很多其他操作系统执行要好。
在应用层对于 open 操作主要使用的是以下两个函数:
(1) int open(const char *pathname, int flags, mode_t mode);
(2) int openat(int dirfd, const char *pathname, int flags, mode_t mode);
复制代码
如果打开文件成功,那么返回文件描述符,值大于或等于0;如果打开文件失败,返 回负的错误号。
下面是该函数参数的说明:
参数 pathname 是文件路径,可以是相对路径(即不以 “/” 开头),也可以是绝对路径(即以 “/” 开头)。
参数 dirfd 是打开一个目录后得到的文件描述符,作为相对路径的基准目录。如果文件路径是 相对路径,那么在函数 openat 中解释为相对文件描述符 dirfd 引用的目录,open 函数中解释为相对 调用进程的当前工作目录。如果文件路径是绝对路径, openat 忽略参数 dirfd。
参数 flags 必须包含一种访问模式: O_RDONLY (只读)、O_ WRONLY (只写)或 O_RDWR(读写)。参数 flags 可以包含多个文件创建标志和文件状态标志。 两组标志的区别是: 文件创建标志只影响打开操作, 文件状态标志影响后面的读写操作。
文件创建标志包括如下:
O_CLOEXEC:开启 close-on-exc标志,使用系统调用 execve() 装载程序的时候关闭文件。
CREAT:如果文件不存在,创建文件。
ODIRECTORY:参数 pathname 必须是一个日录。
EXCL:通常和标志位 CREAT 联合使用,用来创建文件。如果文件已经存在,那么 open() 失败,返回错误号 EEXIST。
NOFOLLOW:不允许参数 pathname 是符号链接,即最后一个分量不能是符号 链接,其他分量可以是符号链接。如果参数 pathname 是符号链接,那么打开失败,返回错误号 ELOOP。
O_TMPFILE:创建没有名字的临时普通文件,参数 pathname 指定目录关闭文件的时候,自动删除文件。
O_TRUNC:如果文件已经存在,是普通文件并且访问模式允许写,那么把文件截断到长度为0。
文件状态标志包括如下:
APPEND:使用追加模式打开文件,每次调用 write 写文件的时候写到文件的末尾。
O_ASYNC:启用信号驱动的输入输出,当输入或输出可用的时候,发送信号通知进程,默认的信号是 SIGIO。
O_DIRECT:直接读写存储设备,不使用内核的页缓存。虽然会降低读写速度, 但是在某些情况下有用处,例如应用程序使用自己的缓冲区,不需要使用内核的页缓存文件。
DSYNC:调用 write 写文件时,把数据和检索数据所需要的元数据写回到存储设备
LARGEFILE:允许打开长度超过 4 GB 的大文件。
NOATIME:调用 read 读文件时,不要更新文件的访问时间。
O_NONBLOCK:使用非阻塞模式打开文件, open 和以后的操作不会导致调用进程阻塞。
PATH:获得文件描述符有两个用处,指示在目录树中的位置以及执行文件描述符层次的操作。 不会真正打开文件,不能执行读操作和写操作。
O_SYNC:调用 write 写文件时,把数据和相关的元数据写回到存储设备。
参数 mode: 参数 mode 指定创建新文件时的文件模式。当参数 flags 指定标志位 O_CREAT 或 O_TMPFILE 的时候,必须指定参数 mode,其他情况下忽略参数 mode。 参数 mode 可以是下面这些标准的文件模式位的组合。
S_IRWXU(0700,以0开头表示八进制):用户(即文件拥有者)有读、写和执行权限。
S_IRUSR(00400):用户有读权限。
S_IWUSR(00200):用户有写权限
S_IXUSR(00100):用户有执行权限。
S_IRWXG(00070):文件拥有者所在组的其他用户有读、写和执行权限
S_IRGRP(00040):文件拥有者所在组的其他用户有读权限。
S_IWGRP(00020):文件拥有者所在组的其他用户有写权限。
S_IXGRP(0010):文件拥有者所在组的其他用户有执行权限。
S_IRWXO(0007):其他组的用户有读、写和执行权限。
S_IROTH(0004):其他组的用户有读权限。
S_IWOTH(00002):其他组的用户有写权限。
S_IXOTH(00001):其他组的用户有执行权限。
参数 mode 可以包含下面这些 Linux 私有的文件模式位:
S_ISUID (0004000):set-user-ID 位。
S_ISGID (0002000):set-group-iD位。
S_ISVTX(0001000):粘滞(sticky)位。
那么我们该如何找到对应的 syscall? 有几个小技巧可以用来帮助我们:
用户空间的方法xxx,对应系统调用层方法则是 sys_xxx;
unistd.h 文件记录着系统调用中断号的信息。
宏定义 SYSCALL_DEFINEx(xxx,…),展开后对应的方法则是 sys_xxx;
方法参数的个数x,对应于 SYSCALL_DEFINEx。
根据第一个小技巧,我们知道我们需要找的函数为:sys_open。 具体代码流程比较复杂,这里使用取巧的方式,找到对应的内核函数,前面提到需要找的的函数 为 sys_open。 这种函数在内核中是通过宏定义 SYSCALL_DEFINEx 展开后得到的。那么可以 利用 source insight 的搜索功能。应用层 open 函数的参数的个数为 3,可以假想先从 SYSCALL_DEFINE3 进行全局搜索。随便选择一个搜索结果,这里假设选择的是 SYSCALL_DEFINE3(mknod,这步主要是为了获取代码格式,把 mknod 改成 open ,然后搜索 SYSCALL_DEFINE3(open。 很快我们就在 kernel\fs\open.c 文件中找到唯一的搜索结果,代码如下:
SYSCALL_DEFINE3
SYSCALL_DEFINE3(open, const char __ user*, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
复制代码
if (force_o_largefile())
flags |= O_LARGEFILE;
复制代码
表示 flags 会在 64 位 Kernel 的情况下强制么设置 O_LARGEFILE 来表示支持大文件。 接着跳转到 do_sys_open 函数。
do_sys_open 系统调用主体
long do_sys_open(int dfd, const char __ user *filename, int flags, umode_t mode){
struct open_flags op;
//检查并包装传递进来的标志位
int fd = build_open_flags(flags, mode, &op);
struct filename * tmp;
if (fd)
return fd;
//用户空间的路径名复制到内核空间
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
//获取一个未使用的 fd 文件描述符
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
//调用 do_filp_open 完成对路径的搜寻和文件的打开
struct file * f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
//如果发生了错误,释放已分配的 fd 文件描述符
put_unused_fd(fd);
//释放已分配的 struct file 数据
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
//绑定 fd 与 f。
fd_install(fd, f);
}
}
//释放已分配的 filename 结构体。
putname(tmp);
return fd;
}
复制代码
fd 是一个整数,它其实是一个数组的下标,用来获取指向 file 描述符的指针, 每个进程都有个 task_struct 描述符用来描述进程相关的信息,其中有个 files_struct 类型的 files 字段,里面有个保存了当前进程所有已打开文件 描述符的数组,而通过 fd 就可以找到具体的文件描述符,之间的关系可以参考下图:
这里的参数已经在上面提到过了,唯一需要注意的是 AT_FDCWD,其定义在 include/uapi/linux/fcntl.h,是一个特殊值(** -100 **), 该值表明当 filename 为相对路径的情况下将当前进程的工作目录设置为起始路径。相对而言, 你可以在另一个系统调用 openat 中为这个起始路径指定一个目录, 此时 AT_FDCWD 就会被该目录的描述符所替代。
build_open_flags 初始化 flags
static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op){
int lookup_flags = 0;
//O_CREAT 或者 `__O_TMPFILE*` 设置了,acc_mode 才有效。
int acc_mode;
// Clear out all open flags we don't know about so that we don't report
// them in fcntl(F_GETFD) or similar interfaces.
// 只保留当前内核支持且已被设置的标志,防止用户空间乱设置不支持的标志。
flags &= VALID_OPEN_FLAGS;
if (flags & (O_CREAT | __ O_TMPFILE))
op->mode = (mode & S_IALLUGO) | S_IFREG;
else
//如果 O_CREAT | __ O_TMPFILE 标志都没有设置,那么忽略 mode。
op->mode = 0;
// Must never be set by userspace
flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC;
// O_SYNC is implemented as __ O_SYNC|O_DSYNC. As many places only
// check for O_DSYNC if the need any syncing at all we enforce it's
// always set instead of having to deal with possibly weird behaviour
// for malicious applications setting only __ O_SYNC.
if (flags & __ O_SYNC)
flags |= O_DSYNC;
//如果是创建一个没有名字的临时文件,参数 pathname 用来表示一个目录,
//会在该目录的文件系统中创建一个没有名字的 iNode。
if (flags & __ O_TMPFILE) {
if ((flags & O_TMPFILE_MASK) != O_TMPFILE)
return -EINVAL;
acc_mode = MAY_OPEN | ACC_MODE(flags);
if (!(acc_mode & MAY_WRITE))
return -EINVAL;
} else if (flags & O_PATH) {
// If we have O_PATH in the open flag. Then we
// cannot have anything other than the below set of flags
// 如果设置了 O_PATH 标志,那么 flags 只能设置以下 3 个标志。
flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
acc_mode = 0;
} else {
acc_mode = MAY_OPEN | ACC_MODE(flags);
}
op->open_flag = flags;
// O_TRUNC implies we need access checks for write permissions
// 如果设置了,那么写之前可能需要清空内容。
if (flags & O_TRUNC)
acc_mode |= MAY_WRITE;
// Allow the LSM permission hook to distinguish append
// access from general write access.
// 让 LSM 有能力区分 追加访问和普通访问。
if (flags & O_APPEND)
acc_mode |= MAY_APPEND;
op->acc_mode = acc_mode;
//设置意图,如果没有设置 O_PATH,表示此次调用有打开文件的意图。
op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
if (flags & O_CREAT) {
//是否有创建文件的意图
op->intent |= LOOKUP_CREATE;
if (flags & O_EXCL)
op->intent |= LOOKUP_EXCL;
}
//判断查找的目标是否是目录。
if (flags & O_DIRECTORY)
lookup_flags |= LOOKUP_DIRECTORY;
//判断当发现符号链接时是否继续跟下去
if (!(flags & O_NOFOLLOW))
lookup_flags |= LOOKUP_FOLLOW; //查找标志设置了 LOOKUP_FOLLOW 表示会继续跟下去。
//设置查找标志,lookup_flags 在路径查找时会用到
op->lookup_flags = lookup_flags;
return 0;
}
复制代码
上面的函数主要是根据用户传递进来的 flags 进一步设置具体的标志,然后把这些标志封装到 open_flags 结构体中。以便后续使用。
接下来就是函数 getname() ,这个函数定义在 fs/namei.c,主体是 getname_flags, 我们捡重点的分析,无关紧要的代码以 ... 略过。
getname_flags 复制路径名
struct filename * getname(const char __ user *filename){
return getname_flags(filename, 0, NULL);
}
复制代码
struct filename {
const char* name;// pointer to actual string ---指向真实的字符串
const __ user char* uptr;// original userland pointer -- 指向原来用户空间的 filename
struct audit_names* aname;
intrefcnt;
const chariname[]; //用来保存 pathname
};
复制代码
struct filename * getname_flags(const char __ user *filename, int flags, int* empty){
struct filename* result;
char* kname;
int len;
// 这里一般来说赋值为 NULL。这里主要是针对Linux 审计工具 audit,我们不管。
result = audit_reusename(filename);
// 如果不为空直接返回。
if (result)
return result;
// 通过__getname 在内核缓冲区专用队列里申请一块内存用来放置路径名(filemname 结构体)
result = __getname();
if (unlikely(!result))
return ERR_PTR(-ENOMEM);
//First, try to embed the struct filename inside the names_cache
//allocation
//kname 指向 struct filename 的 iname 数组。
kname = (char*)result->iname;
// 把 filename->name 指向 iname[0],待会 iname 用来保存用户空间传递过来的路径名(filemname 结构体)。
result->name = kname;
//该函数把用户空间的 filename 复制到 iname
len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX);
//如果复制失败,释放已分配的 result 并返回错误。
if (unlikely(len < 0)) {
__putname(result);
return ERR_PTR(len);
}
// Uh-oh. We have a name that's approaching PATH_MAX. Allocate a
// separate struct filename so we can dedicate the entire
// names_cache allocation for the pathname, and re-do the copy from
// userland.
// 这里判断用户空间传递过来的路径名的长度接近了 PATH_MAX,所以需要分配一个独立的空间
// 用来保存 struct filename 前面的字段,并把 name_cache 全部空间用来保存路径名 (filename->iname)。
//
// #define PATH_MAX 4096 // 4 kb 大小。
// #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE * )0)->MEMBER)
// #define EMBEDDED_NAME_MAX(PATH_MAX - offsetof(struct filename, iname))
// EMBEDDED_NAME_MAX 指的就是:字段 iname 在 filename 结构体中的偏移。
if (unlikely(len == EMBEDDED_NAME_MAX)) {
// 注意,这里是把 iname[1] 的偏移赋值给了 size。这样 size 的大小包含了 inaem[0]
// 可以用来保存 iname 数组的首地址。
const size_t size = offsetof(struct filename, iname[1]);
// 把旧 result 的首地址赋值给了 kanme。
kname = (char * )result;
// size is chosen that way we to guarantee that
// result->iname[0] is within the same object and that
// kname can't be equal to result->iname, no matter what.
// 分配一个独立空间用来保存 filename,这样就可以把 filename 分离出来。
result = kzalloc(size, GFP_KERNEL);
//分配失败,释放资源并返回错误。
if (unlikely(!result)) {
__putname(kname);
return ERR_PTR(-ENOMEM);
}
// 把原来的 filename 的首地址赋值给新分配的 result。这样就实现了分离。
result->name = kname;
// 把用户空间的 filename 复制到 kname(name_cache 起始地址)。
len = strncpy_from_user(kname, filename, PATH_MAX);
// 原来:
// filename struct(内核空间,用 name_cach 来保存)
// result ---> name_cache-----> name
// uptr
// aname
// .... 复制操作(strncpy_from_user())
// iname filename struct(用户空间)
//
// 现在:
// filename struct(内核空间,注意这里是新开独立的空间。)
// result ---> name ------> name_cache filename struct(用户空间)
// uptr 复制操作(strncpy_from_user())
// aname
// ....
// iname
// 新分配的 filename 的首地址指向 name_cach,而 name_cach 又保存了用户
// 空间的 filename,所以新的 filename(result) 能间接访问到用户空间的 filename。
// 复制失败,释放资源,返回。
if (unlikely(len < 0)) {
__putname(kname);
kfree(result);
return ERR_PTR(len);
}
// 路径过长,同样返回错误(从这里也可以看出,在 Linux 中路径名的长度不能超过 4096 字节)。
if (unlikely(len == PATH_MAX)) {
__putname(kname);
kfree(result);
return ERR_PTR(-ENAMETOOLONG);
}
}
// 引用计数为 1
result->refcnt = 1;
// The empty path is special.空路径的处理。
if (unlikely(!len)) {
if (empty)
* empty = 1;
// 如果 LOOKUP_EMPTY 没有设置,也就是本次 open 操作的目标不是空路径,但是传递了一个
// 空路径,所以返回错误。
if (! (flags & LOOKUP_EMPTY)) {
//回收资源
putname(result);
return ERR_PTR(-ENOENT);
}
}
// 指向用户空间的 filename
result->uptr = filename;
result->aname = NULL;
audit_getname(result);
return result;
}
复制代码
struct filename {
const char* name; //pointer to actual string ---指向真实的字符串
const __ user char* uptr;//original userland pointer --- 指向原来用户空间
struct audit_names* aname;
intrefcnt;
const chariname[]; //用来保存 pathname
};
复制代码
首先通过 __getname 在内核缓冲区专用队列里申请一块内存用来放置路径名,其实这块内存就是 一个 4KB 的内存页。这块内存页是这样分配的,在开始的一小块空间放置结构体 struct filename 结构体前面字段的信息,这里我们假设 iname 字段之前的结构使用 struct filename-iname 表示, 之后的空间放置字符串(保存在 iname)。初始化字符串指针 kname,使其指向这个字符串 (iname[])的首地址。然后就是拷贝字符串,返回值 len 代表了 已经 拷贝的字符串长度。如果这个字符串已经填满了内存页剩余空间,就说明该字符串的长度已经大于 4KB - (sizeof(struct filename-iname)了,这时就需要将结构体 struct filename-iname 从这个内存页中分离并单独分配空间,然后用整个内存页保存该字符串。
get_unused_fd_flags 获取 fd
get_unused_fd_flags() 函数用来查找一个可用的 fd(文件描述符)。
int get_unused_fd_flags(unsigned flags){
return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}
复制代码
/*
* allocate a file descriptor, mark it busy.
*/
int __alloc_fd(struct files_struct *files,
unsigned start, unsigned end, unsigned flags)
{
unsigned int fd;
int error;
struct fdtable * fdt;
spin_lock(&files->file_lock);
repeat:
// 通过 files 字段获取 fdt 字段。(该函数考虑了线程竞争,较复杂不展开了。)
fdt = files_fdtable(files);
//从 start 开始搜索
fd = start;
// 进程上一次获取的 fd 的下一个号(fd + 1)保存在 next_fd 中。所以从 next_fd 开始进行查找。
if (fd < files->next_fd)
fd = files->next_fd;
if (fd < fdt->max_fds)
//获取下一个 fd
fd = find_next_fd(fdt, fd);
// N.B. For clone tasks sharing a files structure, this test
// will limit the total number of files that can be opened.
error = -EMFILE;
if (fd >= end)
goto out;
// 获取 fd 后,判断是否需要扩展用来保存 file struct 描述符的数组(fdtable->fd)的容量。
// 返回 0 表示不需要,<0 表示错误,1 表示成功。
error = expand_files(files, fd);
if (error < 0)
goto out;
// If we needed to expand the fs array we
// might have blocked - try again.
// 1,扩容成功,并且重新尝试获取fd
// 因为扩容过程可能会发生阻塞,这期间就有可能其他线程也在获取 fd,所以前面获取的 fd
// 可能被其他线程抢先占用了,因为 Linux 的唤醒是不保证顺序的。
if (error)
goto repeat;
if (start <= files->next_fd)
files->next_fd = fd + 1;
// 在 fdtable->open_fds 位图中置位表示当前获取的 fd 处于使用状态。
// 也就是说当释放该 fd 位图中对应的位清除,从而达到重复使用的的目的。
__set_open_fd(fd, fdt);
// 如果设置了 O_CLOEXEC 标志,那么在 fdtable->close_on_exec 位图对应的位置位。
// 前面提到过开启 close-on-exc 标志,使用系统调用 execve() 装载程序的时候会关闭设置过该标志的文件。
// Linux 中使用 fork() 产生子进程的时候回继承父进程已打开的文件描述符集。execve() 一般就是在子进程
// 里用来运行新程序。
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);
else
__clear_close_on_exec(fd, fdt);
// 设置返回值。
error = fd;
#if 1
// Sanity check 一些合法性检查。
if (rcu_access_pointer(fdt->fd[fd]) != NULL) {
printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
rcu_assign_pointer(fdt->fd[fd], NULL);
}
#endif
out:
spin_unlock(&files->file_lock);
return error;
}
复制代码
struct fdtable {
unsigned int max_fds;
struct file __ rcu ** fd; // current fd array
unsigned long * close_on_exec;
unsigned long * open_fds;
unsigned long * full_fds_bits;
struct rcu_head rcu;
};
复制代码
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */
static inline void __set_open_fd(unsigned int fd, struct fdtable *fdt)
{
__set_bit(fd, fdt->open_fds);
fd /= BITS_PER_LONG;
if (!~fdt->open_fds[fd])
__set_bit(fd, fdt->full_fds_bits);
}
复制代码
这里以 32 位 arm 芯片为例。其中函数 __set_bit 表示以某个地址开始在某个位置 1。 假设我们目前数组的容量为 128 ,那么如下表:共有 4 行,一行 32 列,fd = 32 * row + column。 每个格子中 0 表示当前 fd 没有被占用,1 表示占用了。其中 ... 表示所有的列为 1。 假设我们现在获取的 fd 为 66 也就是第 3 行第 3 列,此时我们可以看到该格子为 0。 调用 __set_bit(fd, fdt->open_fds); 把该位(66)置1,fd /= BITS_PER_LONG; 获取行号 66 / 32 = 2(行号从 0 开始),!~fdt->open_fds[fd], open_fds为 long 类型 指针,也就是说步长为 32 位,相当于取第 3 个 long 数据的值,然后位取反,因为此时该 long 数据 的每一位都置 1 了,所以取反后的值为 0,!0 就为 true 了。此时我们可以确定第 3 行所有的 列都被使用了,所以我们可以把 full_fds_bits 的第 2 位置 1,表示该行已全部被使用。
0
1
2
...
30
31
1
1
1
...
1
1
1
1
1
...
1
1
1
1
0
...
1
1
1
1
1
...
0
0
接下来看找 fd 函数 find_next_fd() 就很简单了。
static unsigned long find_next_fd(struct fdtable *fdt, unsigned long start){
unsigned long maxfd = fdt->max_fds;
// 当前容量最后的一行
unsigned long maxbit = maxfd / BITS_PER_LONG;
// 开始行
unsigned long bitbit = start / BITS_PER_LONG;
// 先找到一个空行(有空闲位的某一行)
bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG;
if (bitbit > maxfd)
return maxfd;
if (bitbit > start)
start = bitbit;
// 在该行上找到一个具体的空位。
return find_next_zero_bit(fdt->open_fds, maxfd, start);
}
复制代码
尽量以自己的能力对每行代码进行了注释,同时只是为了学习内核大神是如何玩转指针以及数据结构。 可以从 __set_open_fd() 函数看出对指针熟练的使用方式,以及快速定位的思想。
-
linux 系统调用(二)源码分析
2019-09-11 19:23:47在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的。在glibc库中,通过封装例程(Wrapper Routine)将API和系统调用关联起来。API是头文件中所... -
从open系统调用的源码看文件的打开过程
2010-06-20 12:40:00转自:中国Linux论坛 原作者:lucian_yao 内容有修订,并给出总结 我们常常使用系统调用open来打开一个文件,例如:fd = open( "/mnt/data/myfile",O_RDWR|O_CREAT);下面来看看Linux是如何完成的,首先是系统调用的... -
linux系统调用之read源码解析(基于linux0.11)
2019-05-03 17:55:21进程通过系统调用,从而进入中断处理,中断处理从系统调用表里找到sys_read函数执行。 int sys_read(unsigned int fd,char * buf,int count) { struct file * file; struct m_inode * inode; if (fd>=NR_OPEN... -
linux系统调用之write源码解析(基于linux0.11)
2019-05-03 18:46:30write函数的部分逻辑和read相似。我们先看入口函数。 int sys_write(unsigned int fd,char * buf,int count) { struct file * file;...=NR_OPEN || count <0 || !(file=current->filp[fd])) ... -
open 系统调用
2012-04-08 15:56:54进程要访问一个文件,必须首先获得一个文件描述符,这是通过open系统调用来完成的。文件描述符是有限的资源,所以在不用时应该及时释放。 该系统调用是用来获得欲访问文件的文件描述符,如果文件并不存在,则还 -
android-linux系统调用原理之整体系统框架实现原理之OPEN实现
2016-11-26 14:31:54android-linux系统调用原理之整体系统框架实现原理之OPEN实现.docx 【 相关源码版本: LINUX内核源码版本:linux-3.0.86 UBOOT版本:uboot-2010.12. Android系统源码版本:Android-5.0.2】 对于安桌系统来... -
linux系统调用的过程
2019-07-04 16:25:11系统调用的过程(从open到sys_open,其实glibc使用的是sys_openat后面会说)3.1 先写一个小程序(test_syscall.c)3.2 查看编译链接后的汇编代码3.3 动态库中的open函数3.4 在动态库的源码中查找open函数(当然是在... -
linux的open,write源码何在?
2013-12-29 22:45:24linux下面的open和write源码在哪来,当然这个源码就是glibc,glibc在编译器中直接以库形式面向用户。 那简单的open其实也会有一个执行过程,在glibc里面有他的源码,最终是做一个SWI软中断的汇编执行过程,调用... -
Linux系统调用
2016-03-17 19:26:57杨明辉 + 原创作品转载请注明出处 + 《Linux...1.进入实验楼,打开终端输入命令cd Code进入Code目录,然后输入>open.c新建文件,并将C语言的源码输入文件中(源码下面给出),然后输入命令gcc main.c -o main -m32对源 -
linux syscall 内核系统调用API接口
2018-05-20 22:39:59linux内核系统调用API源码路径:include\linux\syscalls.h源码:/* * syscalls.h - Linux syscall interfaces (non-arch-specific) * * Copyright (c) 2004 Randy Dunlap * Copyright (c) 2004 Open Source ... -
Linux文件系统(七)---系统调用之open操作(一)
2015-11-17 09:49:32当我们打开一个文件的时候,需要获得文件的文件描述符(前面已经说过其实就是文件数组下标),一般是通过函数open来完成,这个系统调用在头文件中声明定义,我们看一下源码: [cpp] view plaincopyprint... -
linux系统调用表(system call table)
2017-05-30 17:14:00系统调用号 函数名 入口点 源码 0 read sys_read fs/read_write.c 1 write sys_write fs/read_write.c 2 open sys_open fs/open.c 3 close... -
系统调用如何进入内核层次,深入glibc寻找open函数真实实现。
2019-12-14 11:15:16本文跟随上文【Linux为何一切皆文件】,继续探寻应用程序如何通过系统调用,从应用层转换到内核层,本文以open系统调用为例。我们需要查询glibc的源码包[下载处],本文使用的是最新的glibc2.30版本的源码。 -
系统调用从glibc库到内核的追踪(open函数)
2018-08-21 21:49:53前面说了glibc以及标准C库函数和系统调用,但总感觉有点隔靴挠痒,下面就来追踪一下系统调用的源码。(linux-3.3内核,glibc-2.17源码) 系统调用以open函数为例 怎么查呢? 用 man 2 open 查看open的用法,看到需要... -
Linux内核中的dup系统调用
2017-03-28 19:35:53转自... 2、内核初始化中的相关源码分析 static int init(void * unused) { ... if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) printk(KE -
《Linux内核设计与实现》学习【3】——系统调用
2020-10-04 12:46:16系统调用–>硬件。 作用: 1)为用户空间提供了一种硬件的抽象接口 2)保证安全性,内核可以根据权限、用户类型等对需要的访问进行裁决 2 源码分析 以arm平台代码为例。 应用程序调用open、read... -
linux内核 0.11版本源码 带中文注释
2009-08-31 15:10:21// Linux 的系统调用中断0x80。该中断是所有系统调用的 // 入口。该条语句实际上是int fork()创建进程系统调用。 // syscall0 名称中最后的0 表示无参数,1 表示1 个参数。 static inline _syscall0 (int, pause... -
Linux内核源码+电子书
2011-02-21 15:13:108.7.1 open 系统调用 8.7.2 read 系统调用 8.7.3 fcntl 系统调用 8 .8 Linux2.4文件系统的移植问题 第九章 Ext2文件系统 9.1 基本概念 9.2 Ext2的磁盘布局和数据结构 9.2.1 Ext2的磁盘布局 ... -
Linux内核源码阅读之打开文件篇
2013-08-05 10:04:06Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数: SYSCALL_DEFINE3... -
系统调用定义宏 SYSCALL_DEFINEx 的分析
2020-09-17 07:35:59其实, Linux内核中的系统调用, 都是经过一个叫SYSCALL_DEFINEx的宏展开出来的, 没展开之前, 是无法搜到sys_xxx()的. 本文就是分析定义系统调用的宏SYSCALL_DEFINEx的实现. 这里以open()这个系统调用为例 -
mount 内核源码_Linux 内核源码分析 -- chroot
2020-12-23 12:54:25chroot 在 内核中的实现在 Linux 5.6 版本中 chroot 函数的系统调用对应的函数位于:./fs/open.c:539:SYSCALL_DEFINE1(chroot, const char __user *, filename)SYSCALL_DEFINE1(chroot, const char __user *, ...
-
css >选择器
-
Docker从入门到精通
-
凸优化1:什么是凸优化问题
-
C++对象模型.zip
-
自定义thumb上带文本显示的拖动条Demo.zip
-
clickhouse20.9.2.20-2..zip
-
图形衬底参数对LED发光效率的影响
-
RFC3261-中文版本.zip
-
超详细Mybatis学习笔记(可供下载)
-
IOS开发之——手势解锁-圆的连线(85)
-
仿芝麻信用示例Demo.zip
-
注册表转换工具(reg to bat、vbs).rar
-
MHA 高可用 MySQL 架构与 Altas 读写分离
-
方法入门
-
vue解决控制http的referer有无
-
看了这篇文章,你就会在Windows和Ubuntu上快速安装MongoDB
-
网络视频音频整理转成文字,你还在自己整理?——微转写
-
spark大数据分析与实战
-
Mycat 实现 MySQL的分库分表、读写分离、主从切换
-
084_可直接用于项目的qt窗口(桑原创).rar