1. 背景
之前一直对文件描述符,重定向符号的含义不是很清楚。今天整理了下文件描述符的背景知识,并实际操作了文件描述符,对其理解有了更深入的认识。
2. 文件元数据Inode
Inode保存文件的元数据。分别是:Inode编号、文件权限、文件的拥有者的UID、文件所属组的GID、硬链接数、文件的大小、文件的间戳、指向磁盘文件的数据块指针等。其信息如下图所示:
通过linux的stat命令可以查看文件的inode信息:
参数解释如下:
- File:显示文件名
- Size:显示文件大小
- Blocks:文件使用的数据块总数
- IO Block:IO块大小
- regular file:文件类型(常规文件)
- Device:设备编号
- Inode:Inode号
- Links:链接数
- Access:文件的权限
- Gid、Uid:文件所有权的Gid和Uid
- access time:表示我们最后一次访问(仅仅是访问,没有改动)文件的时间
- modify time:表示我们最后一次修改文件的时间
- change time:表示我们最后一次对文件属性改变的时间,包括权限,大小,属性等等
- Birth time : 文件创建时间,crtime,不过据查此属性linux已废弃,目前状态显示结果均为-
3. 目录与文件名
文件名并不是保存在Inode中,而是保存在父目录的block中。通过父目录的Inode可以定位到block,在block中可以找到父目录包含的子文件名与Inode的对应关系。
4. 文件描述符
在linux中,内核通过inode来找到每个文件,但一个文件可以被许多用户同时打开或一个用户同时打开多次。这就有一个问题,如何管理文件的当前位移量,因为可能每个用户打开文件后进行的操作都不一样,这样文件位移量也不同,当然还有其他的一些问题。所以linux又搞了一个文件描述符(file descriptor)这个东西,来分别为每一个用户服务。每个用户每次打开一个文件,就产生一个文件描述符,多次打开就产生多个文件描述符,一一对应,不管是同一个用户,还是多个用户。该文件描述符就记录了当前打开的文件的偏移量等数据。所以一个i节点可以有0个或多个文件描述符。多个文件描述符可以对应一个i节点。
打开现有文件或新建一个文件时,内核会返回一个文件描述符,文件描述符用来指定已打开的文件。内核维护了文件描述符表、打开文件表和Inode表,描述了如何通过文件描述符找到文件的物理位置。
- 文件描述符表维护进程打开的文件。一条记录为一个句柄。
- 打开文件表维护所有打开的文件。一条记录为一个句柄。
- Inode表维护所有Inode的元数据信息。
每个表的字段内容如下:
3个表的调用关系如下:
5. 重定向符号
重定向符号有<、>、<>。可以通过以下例子理解:
-
2>&1>/dev/null:先定义2>&1,此时表示错误输出和标准输出都打印到屏幕上;再定义1>/dev/null,表示标准输出丢弃。
- >/dev/null 2>&1:先定义1>/dev/null,表示标准输出丢弃;再定义2>&1,由于标准输出已经表示要丢弃了,此时表示错误输出也丢弃。
效果如下:
备注:& 发信号通知 Bash 1 是目标文件描述符。
例如:
- cat file等价于cat 0< file,即将file文件内容作为标准输入,即以只读方式打开file文件,cat则打印该输入。
- cat 0<> file表示以读写方式打开file文件,由于0是stdin,无法产生输出,因此cat 0<> file和 cat 0< file无区别。注意:<>是一个重定向字符,表示将将file文件的输入输出重定向到文件描述符0中。
6. 文件操作底层流程
6.1 打开文件
6.2 关闭文件
7. 文件描述符实践
目的:读写socket,向百度发送HTTP请求,并获得百度的响应。
7.1 建立链接
创建一个socket文件,打开该socket文件,在文件描述符表中创建一个句柄,使用文件描述符表示该句柄:
//当指定目录为/dev/tcp/www.baidu.com/80时,触发内核机制,本机与www.baidu.com的80端口建立socket连接;socket是一个文件,将socket文件的输入输出重定向到文件描述符8中。<>是一个符号,表示以读写方式打开socket文件。 exec 8<> /dev/tcp/www.baidu.com/80查看进程的文件描述符,发现已经创建了8这个文件描述符:
命令解释:
exec: shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。 因此,如果在一个shell里面执行exec ls;那么,当列出了当前目录后,这个shell就自己退出了,因为这个shell进程已被替换为仅仅执行ls命令的一个进程,执行结束自然也就退出了。 为了避免这个影响我们的使用,一般将exec命令放到一个shell脚本里面,用主脚本调用这个脚本,调用点处可以用bash a.sh,(a.sh就是存放该命令的脚本),这样会为a.sh建立一个sub shell去执行,当执行到exec后,该子脚本进程就被替换成了相应的exec的命令。 source命令或者”.”,不会为脚本新建shell,而只是将脚本包含的命令在当前shell执行。 不过,要注意一个例外,当exec命令来对文件描述符操作的时候,就不会替换shell,而且操作完成后,还会继续执行接下来的命令。
exec总结:
7.2 发送请求
echo -e "GET / HTTP/1.0\n" >&8解释:
- echo -e表示要转译\n为一个空行。
- >&8等价于1>&8,表示标准输出重定向到文件描述符8中,即重定向到socket文件中。
7.3 获取响应
cat <&8解释:
- cat <&8,等价于cat 0<&8:表示将文件描述符8的内容作为标准输入,输出到标准输出设备(即屏幕)上。即将socket文件内容打印到屏幕上。
百度返回的信息如下: