Linux:Bash中的文件描述符详解

相关阅读

Linuxicon-default.png?t=O83Ahttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm=1001.2014.3001.5482


        Linux中的所有进程,都拥有自己的文件描述符(File Descriptor, FD),它是操作系统在管理进程和文件时的一种抽象概念。每个文件描述符由一个非负整数表示,用来标识进程已打开的文件、输入输出流、网络套接字等资源。一个进程可以打开的文件描述符是有上限的,可以通过ulimit命令查询,如例1所示。

# 例1
zhangchen@test:~$ ulimit -n # 查询当每个进程的文件描述符数量上限
1048576

        每个正在运行的进程,都会在虚拟文件系统的目录/proc下用一个子目录表示,目录名为进程的id号。当一个进程创建时,操作系统会为其分配一个未使用的id号并在目录/proc下创建相应的目录;当一个进程执行完毕退出时,操作系统会删除相应的目录并回收id号。

        在目录/proc/pid/fd(pid指具体的进程id号)中,可以找到名为0、1、2...的链接文件,它们指向了相应的文件描述符代表的资源,例2展示了如何查看当前Bash进程的文件描述符。

# 例2
zhangchen@test:~$ ps   # 查询Bash进程的id号PID TTY          TIME CMD
2556994 pts/3    00:00:00 bash
2557252 pts/3    00:00:00 ps
zhangchen@test:~$ ls -al /proc/2556994/fd   # 显示虚拟文件系统中bash进程的文件描述符目录
lrwx------ 1 zhangchen test 64  9月 20 13:53 0 -> /dev/pts/3
lrwx------ 1 zhangchen test 64  9月 20 13:53 1 -> /dev/pts/3
lrwx------ 1 zhangchen test 64  9月 20 13:53 2 -> /dev/pts/3
lrwx------ 1 zhangchen test 64  9月 20 13:53 255 -> /dev/pts/3

        其中文件描述符0、1、2尤为重要,它们是所有进程在创建时就默认拥有的文件描述符,分别表示标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。从例2中可以看出它们都指向了/dev/pts/0这个伪终端设备(Pseudo-Terminal Slave),这是因为该终端是从GUI界面启动的(ssh远程连接的终端也是如此),如果是利用Ctrl+Alt+F*启动的终端,则会显示是/dev/tty*之类的设备。

        在目录/dev下可以找到三个链接文件stdin、stdout和stderr,它们指向了当前进程的文件描述符0、1、2,如例3所示。

# 例3
zhangchen@test:~$ ls -al /dev/std* # 查询标准输入、输出、错误设备
lrwxrwxrwx 1 root root 15  7月 12 17:37 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15  7月 12 17:37 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15  7月 12 17:37 /dev/stdout -> /proc/self/fd/1

        其中/proc/self是一个链接文件,指向了当前进程的目录,也就是说如果使用ls /proc命令,则显示其指向的是进程ls的目录,如例4所示。

# 例4
zhangchen@test:~$ ls -al /proc/self  # 查询当前进程(即ls)的信息
lrwxrwxrwx 1 root root 0  7月 12 17:37 /proc/self -> 2557940 # 指向了/proc/2557940zhangchen@test:~$ ls -al /proc/self  # 查询当前进程(即ls)的信息
lrwxrwxrwx 1 root root 0  7月 12 17:37 /proc/self -> 2557972 # 指向了/proc/2557972zhangchen@test:~$ ls -al /proc/self  # 查询当前进程(即ls)的信息
lrwxrwxrwx 1 root root 0  7月 12 17:37 /proc/self -> 2557975 # 指向了/proc/2557975

        从例4中可以看出 ,连续三次使用ls命令得到的结果是不同的,这是因为每次执行ls命令都会创建一个新的进程并分配给一个未使用的id号(它们可能相等,因为执行完毕后id号会被回收,但在该例中不相等)。
        有些偏题了,我们回到文件描述符,当创建了一个新的终端并查询其文件描述符时,会发现文件描述符0、1、2指向了另一个伪终端设备/dev/pts/8,如例5所示。

# 例5
zhangchen@test:~$ ps   # 查询Bash进程的id号PID TTY          TIME CMD
2559706 pts/3    00:00:00 bash
2559728 pts/3    00:00:00 ps
zhangchen@test:~$ ls -al /proc/2559706/fd   # 显示虚拟文件系统中Bash进程的文件描述符目录
lrwx------ 1 zhangchen test 64  9月 20 13:54 0 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 1 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 2 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 255 -> /dev/pts/8

        默认情况下,子进程被创建并替换后会继承父进程的文件描述符(可以通过设置FD_CLOEXEC标志改变替换后是否继承文件描述符),为了进行验证,首先介绍一个命令exec。exec命令可以用于进程替换,也可用于操作Bash进程的文件描述符,如例6所示。在此基础上如果使用sleep 100 &命令,查询其文件描述符会发现与Bash进程的相同,如例7所示。

# 例6
zhangchen@test:~$ exec 3> output.txt   # 在当前Bash进程以写方式打开output.txt文件,分配文件描述符3
zhangchen@test:~$ ps   # 查询Bash进程的id号PID TTY          TIME CMD
2559706 pts/3    00:00:00 bash
2559947 pts/3    00:00:00 ps
zhangchen@test:~$ ls -al /proc/2559706/fd   # 显示虚拟文件系统中Bash进程的文件描述符目录
lrwx------ 1 zhangchen test 64  9月 20 13:54 0 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 1 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 2 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 13:54 255 -> /dev/pts/8
l-wx------ 1 zhangchen test 64  9月 20 17:02 3 -> /home/zhangchen/output.txt
# 例7
zhangchen@test:~$ sleep 100 & # 一个后台执行的测试命令
[1] 2560074
zhangchen@test:~$ ls -al /proc/2560074/fd   # 显示虚拟文件系统中sleep进程的文件描述符目录
lrwx------ 1 zhangchen test 64  9月 20 17:03 0 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 17:03 1 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 17:03 2 -> /dev/pts/8
lrwx------ 1 zhangchen test 64  9月 20 17:03 255 -> /dev/pts/8
l-wx------ 1 zhangchen test 64  9月 20 17:03 3 -> /home/zhangchen/output.txt

        例8展示了在Python中打开一个文件,并显示其文件描述符。

# 例8
# 文件:test.pyimport time
file = open('example.txt', 'w') # 打开文件
fd = file.fileno() # 获取文件描述符
print("File descriptor assigned: {}".format(fd)) # 输出文件描述符
time.sleep(60) # 等待60秒
file.close() # 关闭文件zhangchen@test:~$ python test.py & # 一个后台执行的Python进程
[2] 11491
File descriptor assigned: 3
zhangchen@test:~$ ls -al /proc/11491/fd   # 显示虚拟文件系统中python进程的文件描述符目录
lrwx------ 1 zhangchen test 64  9月 20 17:06 0 -> /dev/pts/0
lrwx------ 1 zhangchen test 64  9月 20 17:06 1 -> /dev/pts/0
lrwx------ 1 zhangchen test 64  9月 20 17:06 2 -> /dev/pts/0
l-wx------ 1 zhangchen test 64  9月 20 17:06 3 -> /home/zhangchen/example.txt

        看起来像是Linux会选择优先当前未使用最小的文件描述符,这是对的!但是否Python进程只打开过example.txt一个文件?答案是否定的(显然,Python进程肯定还打开了test.py文件)。

        一个命令的执行可能牵涉到多次打开文件、关闭文件的过程,例9使用了strace命令观察了cat命令执行的过程。

# 例9
[zhangchen@EDA Desktop]$ strace cat tt
execve("/usr/bin/cat", ["cat", "tt"], 0x7fff10389408 /* 67 vars */) = 0
brk(NULL)                               = 0x1472000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8c9f2f0000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/tls/x86_64", 0x7ffe8d6e3750) = -1 ENOENT (No such file or directory)
open("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/tls", 0x7ffe8d6e3750) = -1 ENOENT (No such file or directory)
open("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/x86_64", 0x7ffe8d6e3750) = -1 ENOENT (No such file or directory)
open("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/opt/Synopsys/LC2018/lc/O-2018.06-SP1/linux64/lc/shlib", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=126574, ...}) = 0
mmap(NULL, 126574, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8c9f2d1000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156240, ...}) = 0
mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8c9ed02000
mprotect(0x7f8c9eec5000, 2097152, PROT_NONE) = 0
mmap(0x7f8c9f0c5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f8c9f0c5000
mmap(0x7f8c9f0cb000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8c9f0cb000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8c9f2d0000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8c9f2ce000
arch_prctl(ARCH_SET_FS, 0x7f8c9f2ce740) = 0
mprotect(0x7f8c9f0c5000, 16384, PROT_READ) = 0
mprotect(0x60b000, 4096, PROT_READ)     = 0
mprotect(0x7f8c9f2f1000, 4096, PROT_READ) = 0
munmap(0x7f8c9f2d1000, 126574)          = 0
brk(NULL)                               = 0x1472000
brk(0x1493000)                          = 0x1493000
brk(NULL)                               = 0x1493000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, ...}) = 0
mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8c987c0000
close(3)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
open("tt", O_RDONLY)                    = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=6, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
read(3, "test\n\n", 65536)              = 6
write(1, "test\n\n", 6test)                 = 6
read(3, "", 65536)                      = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

        可以看出,cat命令在执行时,打开过三类文件(标准输入、输出和错误不用打开,因为它们继承自父进程),而且文件描述符的值其实就是系统调用open函数的返回值。

# 动态链接相关的文件
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 # 分配文件描述符3
*****
close(3) # 关闭文件描述符3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 # 分配文件描述符3
*****
close(3) # 关闭文件描述符3# 本地语言环境文件
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 # 分配文件描述符3
*****
close(3) # 关闭文件描述符3# 目标文件(tt)
open("tt", O_RDONLY) = 3 # 分配文件描述符3
*****
close(3) # 关闭文件描述符3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149085.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

开关电源自动测试系统的测试设备与特色

突破传统测试系统的操作维护困难等限制,NSAT-8000开关电源自动测试系统以其开放式架构和0代码模式,带来了不一样的开关电源自动化测试体验。 开关电源自动测试系统的测试设备 开关电源自动测试系统核心硬件包括:可编程交直流电源、电子负载、…

使用 HFD 加快 Hugging Face 模型和数据集的下载,解决443报错

Hugging Face 提供了丰富的预训练模型和数据集,而且使用 Hugging Face 提供的 from_pretrained() 方法可以轻松加载它们,但是,模型和数据集文件通常体积庞大,用默认方法下载起来非常花时间。 本文将指导你如何使用 HFD&#xff08…

小新 Pro13 + windows 11 家庭中文版(网络适配器及地址配置)

网络适配器位置及地址配置 网络适配器简介 计算机系统:网络适配器详解,全面剖析 网络适配器位置 不同于win11之前的版本,win11的网络适配器的位置如下: 1、右键 右下角的网络图标-》网络和internet设置-》高级网络设置-》可以…

冒泡排序bubble sort

冒泡排序(bubble sort)通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。 算法流程 def bubble_sort(nums: list[int]):"""冒泡排序"""n len(nums)# 外循环&…

46.哀家要长脑子了!

1.435. 无重叠区间 - 力扣(LeetCode) 方法一:动态规划 实际上本质就是找最长的无重叠子序列,那么我们可以遍历这个区间的集合,只要前一个区间的右端点是小于等于后一个区间的左端点,那么这两个区间就不是重…

如何将Excel表格嵌入Web网页在线预览、编辑并保存到自己服务器上?

猿大师办公助手作为一款专业级的网页编辑Office方案,不仅可以把微软Office、金山WPS和永中Office的Word文档内嵌到浏览器网页中实现在线预览、编辑保存等操作,还可以把微软Office、金山WPS和永中Office的Excel表格实现网页中在线预览、编辑并保存到服务器…

虚拟机:4、配置12.5的cuda和gromacs

前言:本机环境是win11,通过wsl2安装了ubuntu实例并已实现gpu直通,现在需要下载12.5的cuda 一、查看是否有gpu和合适的cuda版本 在ubuntu实例中输入 nvidia-smi输出如下: 说明该实例上存在gpu驱动,且适合的CUDA版本…

硬件测试(五):信号补偿

一、简介 高速信号的趋肤效应以及传输线的介质损耗,使信号在传输过程中衰减很大,导致最后得到的信号失真。为了在接收终端能得到比较好的波形,就需要对受损的信号进行补偿,常用的补偿技术有:预加重、去加重和均衡三种信…

思科安全网络解决方案

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

告别xx搜索,我用这个AI工具...

点击“终码一生”,关注,置顶公众号 每日技术干货,第一时间送达! 前段时间,逛 GitHub 的时候发现了一个评估报告,对AI搜索引擎进行了详细的准确性测试,覆盖6种主流语言和5类场景。 其中&#xf…

苍穹外卖上半部分总结

苍穹外卖一个很经典的项目 虽然已经烂大街,但项目依旧是很优秀,并且代码十分规范,很值得学习。 前置介绍 niginx反向代理 前端和后端的url请求不一致的原因:前端是请求到nginx服务器,再由nginx服务器转发到后端 ngi…

箭头与数字识别系统源码分享

箭头与数字识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

《CUDA编程》1.GPU硬件与CUDA环境搭建

1 GPU 介绍 GPU(graphics processing unit),意为图形处理器,也被称为显卡(graphics card)。GPU的浮点数运算峰值就比同时期的CPU高一个量级;GPU的内存带宽峰值也比同时期的CPU高一个量级。 CP…

数据结构 - 排序算法

一.直接插入排序 /*** description: 直接插入排序算法* param - a : 要进行排序的数组的指针* return : 无 */ void Seqsort(int *a) {/* i 用于表示无序部分的第一个元素的下标 , j 用于表示有序部分的最后一个元素的下标 &…

如何登录通义灵码,快速开启AI编码之旅?

通义灵码个人版开发者可以使用阿里云账号登录通义灵码 IDE 端插件,本文介绍个人版开发者登录 IDE 端插件的操作指南。 登录通义灵码 步骤 1:准备工作 已成功注册阿里云账号,具体操作可参考:账号注册(PC端)…

15.多线程概述(下篇)

目录 1.进程与线程 2.实现多线程方式一:继承Thread类【应用】 3.实现多线程方式二:实现Runnable接口【应用】 4.实现多线程方式三:实现Callable接口【应用】 5.三种实现方式的对比与套路 6.设置和获取线程名称/线程对象【应用】 7.线程优先级…

【编程底层原理】Java常用读写锁的使用和原理

一、引言 在Java的并发世界中,合理地管理对共享资源的访问是至关重要的。读写锁(ReadWriteLock)正是一种能让多个线程同时读取共享资源,而写入资源时需要独占访问的同步工具。本文将带你了解读写锁的使用方法、原理以及它如何提高…

【重磅】考虑火电机组储热改造的电力系统低碳经济调度

目录 1 主要内容 储热改造原理 约束条件 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序参考文献《考虑火电机组储热改造的电力系统低碳经济调度》,利用原文献火电机组储热改造方案建立模型,在传统火电机组的基础上加装热能存储系统&#xf…

【每天学个新注解】Day 3 Lombok注解简解(二)—@Log

Log 自动创建并初始化日志记录器 日志系列注解包括:CommonsLog、Flogger、JBossLog、Log、Log4j、Log4j2、Slf4j、XSlf4j、CustomLog,对应于不同的日志框架。每个注解都会在编译时生成一个名为 log 的静态字段,该字段被初始化为对应的日志框…

【小白向】怎么去除视频水印?HitPaw帮你轻松解决

序言 HitPaw是一款优秀的去除视频水印的工具。 特点:不仅仅能够去除图片、视频里的固定水印,还能去除移动水印。 尤其是它的AI去水印功能,效果非常好。 极简使用教程 下载安装 HitPaw需要在电脑上安装软件才能使用。 支持Windows系统和…