Linux内核之系统调用

Linux系统调用是用户空间与内核空间交互的关键,尤其在文件操作中扮演重要角色。当我们使用open系统调用时,会进入内核空间访问task_struct和files_struct。每个文件都有一个文件描述符(fd),与file结构体相关联,保存文件位置等信息。file对象形成系统打开文件表,其引用计数决定何时释放。进程通过文件描述符表访问file对象,实现文件操作。在进程复制(如fork)或系统调用(如dup、dup2)时,文件描述符会被复制或重新分配。此外,close系统调用会根据文件类型执行不同操作,释放相关资源。通过对系统调用的深入剖析,我们可以更好地理解和管理Linux系统中的文件资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于系统调用,我们必须清楚linux内存分为用户空间和内核空间,当我们进行程序应用时用的是用户空间,当我们要打开文件和设备的时候就会使用系统调用来访问内核空间,这时候相当中断一样,我们进入内核空间,当 处理完以后才回到用户空间。

以下分析个例子关于open的系统调用

int fd = open("abc.txt",O_CREAT);

if(fd != NULL)

{

    printf("file open:fd\n",fd);

}

close(fd);

这时候,当我们使用open系统调用的时候,就会进入内核空间,访问结构体task_struct,而task_struct里面有一个files_struct,

files_struct
{
  atomic_t count;// count为文件表files_struct的引用计数

  fd[1024];

}

此时返回出来是一个fd[3]

而我们输出的结果正是file open: 3

总的来说,当我们使用系统调用的时候,它会去找task结构体,然后找到我们的file结构体,然后再返回出来fd。

下面来源: https://www.cnblogs.com/zengyiwen/p/5755186.html

每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改。Linux中专门用了一个数据 结构file来保存打开文件的文件位置,这个结构称为打开的文件描述(open file description)。这个数据结构的设置是煞费苦心的,因为它与进程的联系非常紧密,可以说这是VFS中一个比较难于理解的数据结构,file结构中主要保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表,其最大长度是NR_FILE,在fs.h中定义为8192。

  1. struct file 
  2. {
  3.  
  4. struct list_head f_list; /*所有打开的文件形成一个链表*/
  5.  
  6. struct dentry *f_dentry; /*指向相关目录项的指针*/
  7.  
  8. struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
  9.  
  10. struct file_operations *f_op; /*指向文件操作表的指针*/
  11.  
  12. mode_t f_mode; /*文件的打开模式*/
  13.  
  14. loff_t f_pos; /*文件的当前位置*/
  15.  
  16. unsigned short f_flags; /*打开文件时所指定的标志*/
  17.  
  18. unsigned short f_count; /*使用该结构的进程数*/
  19.  
  20. unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
  21.  
  22. /*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/
  23.  
  24. int f_owner; /* 通过信号进行异步I/O数据的传送*/
  25.  
  26. unsigned int f_uid, f_gid; /*用户的UID和GID*/
  27.  
  28. int f_error; /*网络写操作的错误码*/
  29.  
  30.  
  31.  
  32. unsigned long f_version; /*版本号*/
  33.  
  34. void *private_data; /* tty驱动程序所需 */
  35.  
  36. };

内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述表中每一项都是一个指针,指向一个用于 描述打开的文件的数据块———file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的 file对象。需要注意的是,file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件。 file对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同 一个file对象的进程.

file对象中包含一个指针,指向dentry对象。dentry对象代表一个独立的文件路径,如果一个文件路径被打开多次,那么会建立多个file对象,但它们都指向同一个dentry对象。

 

dentry对象中又包含一个指向inode对象的指针。inode对象代表一个独立文件。因为存在硬链接与符号链接,因此不同的dentry 对象可以指向相同的inode对象.inode 对象包含了最终对文件进行操作所需的所有信息,如文件系统类型、文件的操作方法、文件的权限、访问日期等。

 

打开文件后,进程得到的文件描述符实质上就是文件描述符表的下标,内核根据这个下标值去访问相应的文件对象,从而实现对文件的操作。

 

注意,同一个进程多次打开同一个文件时,内核会创建多个file对象。

当进程使用fork系统调用创建一个子进程后,子进程将继承父进程的文件描述符表,因此在父进程中打开的文件可以在子进程中用同一个描述符访问。

---------------------------------------------------------------open解析---------------------------------------------------

  1. int open(const char *pathname, int flags);
  2. int open(const char *pathname, int flags, mode_t mode);

前一个是glibc封装的函数,后一个是系统调用

open源码追踪:

  1. long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
  2. {
  3. struct open_flags op;
  4. /* flags为用户层传递的参数, 内核会对flags进行合法性检查, 并根据mode生成新的flags值赋给 lookup */
  5. int lookup = build_open_flags(flags, mode, &op);
  6. /* 将用户空间的文件名参数复制到内核空间 */
  7. char *tmp = getname(filename);
  8. int fd = PTR_ERR(tmp);
  9. if (!IS_ERR(tmp)) {
  10. /* 未出错则申请 新的文件描述符 */
  11. fd = get_unused_fd_flags(flags);
  12. if (fd >= 0) { /* 申请新的文件管理结构file */
  13. struct file *f = do_filp_open(dfd, tmp, &op, lookup);
  14. if (IS_ERR(f)) {
  15. put_unused_fd(fd);
  16. fd = PTR_ERR(f);
  17. } else {
  18. /* 产生文件打开的通知事件 */
  19. fsnotify_open(f);
  20. /* 将文件描述符fd与文件管理结构file对应起来, 即安装 */
  21. fd_install(fd, f);
  22. }
  23. }
  24. putname(tmp);
  25. }
  26. return fd;
  27. }

从上面来看,打开文件,内核消耗了2种资源:文件描述符跟内核管理文件结构file

 

根据POSIX标准,当获取一个新的文件描述符时,要返回最低的未使用的文件描述符。Linux是如何实现这一标准的呢?

在Linux中,通过do_sys_open->get_unused_fd_flags->alloc_fd(0,(flags))来选择文件描述符,代码如下

  1. int alloc_fd(unsigned start, unsigned flags)
  2. {
  3. struct files_struct *files = current->files;//获取当前进程的对应包含文件描述符表的结构
  4. unsigned int fd;
  5. int error;
  6. struct fdtable *fdt;
  7. /* files为进程的文件表, 下面需要更改文件表, 所以需要先锁文件表 */
  8. spin_lock(&files->file_lock);
  9. repeat:
  10. /* 得到文件描述符表 */
  11. fdt = files_fdtable(files);
  12. /* 从start开始, 查找未用的文件描述符。在打开文件时, start为0 */
  13. fd = start;
  14. /* files->next_fd为上一次成功找到的fd的下一个描述符。使用next_fd, 可以快速找到未用的文件描述符;*/
  15. if (fd < files->next_fd)
  16. fd = files->next_fd;
  17. /*
  18. 当小于当前文件表支持的最大文件描述符个数时, 利用位图找到未用的文件描述符。
  19. 如果大于max_fds怎么办呢?如果大于当前支持的最大文件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值