深入理解Linux下的几种文件类型

Linux系统下,有七类文件类型:

普通文件(-)

目录(d)

软链接(字符链接L)

套接字文件(S)

字符设备(S)

块设备(B)

管道文件(命名管道P)

普通文件、目录、软链接无需多解释。

管道文件

管道分为匿名管道和命名管道。管道都是一端写入、另一端读取,它们是单方向数据传输的,它们的数据都是直接在内存中传输的,管道是进程间通信的一种方式,例如父进程写,子进程读。

在shell中匿名管道就是一个管道符号"|",例如ls | grep xxx,其中ls对应的进程是这个独立进程组中的父进程,grep对应的进程是子进程,父进程写子进程读。

在编程语言中,匿名管道是通过创建两个文件句柄或文件描述符(例如A、B)来实现的,一个文件句柄用于写数据(例如A写入端,数据写入A将自动推入B中),另一个文件句柄用于读数据(即B)。

对于命名管道,即有名称的管道,命名管道将文件保留在文件系统中,它也称为FIFO,也就是first in first out。虽然命名管道文件保留在文件系统中,但是这个文件只是使用命名管道的一个入口,在使用命名管道传输数据的时候,仍然是在内存中进行的,也就是说并不会因为保留在文件系统上命名管道的效率就低了。

在shell中,可以使用mknod命令或mkfifo命令创建命名管道,在写某些特殊需求的shell脚本时,命名管道非常有用。实际上,在Bash 4之后就支持协程(使用coproc命令)的功能了(ksh和zsh老早就支持协程),但是协程的需求都能通过命名管道来实现。

一般的管道都是单向通信的,无法实现双向通信的功能,也就是只能一边写一边读,不能两边都能读、写。如果要实现双向通信,可以创建两根管道(这样就有4个文件句柄,两个读端,两个写端),或者使用更方便的套接字。

套接字(Socket)

套接字用来实现两端通信,正如上面分析的,可以实现双向管道的进程间通信功能。不仅如此,套接字还能通过网络实现跨主机的进程间通信功能。

套接字需要成对才有意义,也就是分为两端,每一端都有用于读、写的文件描述符(或文件句柄),相当于两根双向通信的管道。

套接字根据协议族的方式分为两大类:网络套接字(AF_INET类型,根据ipv4和ipv6分为inet4和inet6)和Unix Domain套接字(AF_UNIX类型)。当然,从协议族往下,套接字可细分为很多种类型,例如INET套接字可以分为TCP套接字、UDP套接字、链路层套接字、Raw套接字等等。其中网络套接字是网络编程的基础和核心。

Unix Domain套接字

对于单机的进程间通信,使用Unix Domain套接字比Inet套接字更好,因为Unix Domain套接字没有网络通信组件,也就是少了很多网络功能,它更加轻量级。实际上,某些语言在某些操作系统平台上实现的管道功能就是通过Unix Domain来实现的,可想而知其高效率。

Unix Domain套接字有两个文件句柄(例如A、B),这两个文件句柄都是同时可读、可写的句柄。进程1向A写入数据,将自动推送到B上,进程2可从B上读取从A写入的数据,同理进程2向B中写入数据将自动推送到A上,进程1可从A上读取从B写入的数据。如下:

1 2 3 4 进程1 进程2 ------------------------ A -----------> B B -----------> A

在编程语言中,创建Unix Domain Socket自然有对应的函数轻松创建(可man socketpair)。对于bash shell,可以通过nc命令(NetCat)来创建,或者干脆使用两个命名管道来实现对应的功能。如有需要,可自行了解如何在bash shell中使用Unix Domain套接字。

网络套接字

对于跨网络的进程间通信,需要使用网络套接字。每个网络套接字都由5部分组成,它们称为套接字的5元组。格式如下:

{protocol, src_addr, src_port, dest_addr, dest_port}

即协议、源地址、源端口、目标地址、目标端口。

每端套接字在内核空间都有两个buffer(即一对socket有4个buffer),每一端都有recv buffer和send buffer。进程1向自己的套接字的send buffer写入数据,将发送到对端的recv buffer中,然后对端的进程2就可以从recv buffer中读取数据,反之亦然。

但是在真正可以读、写网络套接字之前,网路套接字还需要一些设置。服务端套接字创建(socket()函数,创建后就会有一个文件句柄或文件描述符供读、写操作)后,还要绑定地址(通过bind()函数)和监听端口(通过listen()函数),客户端则只需要创建套接字后,直接使用connect()函数向服务端套接字发起连接请求即可。

对于TCP套接字,客户端发起连接请求即表示要和服务端进行三次握手(内核完成,和用户空间进程无关)。将这三次握手的每一次进行细分,第一次客户端发送SYN请求,服务端接收到SYN后,内核将这个连接放进syn queue中并设置状态为syn-recv,然后发送ack+syn给客户端,当接收到客户端回复ack后,内核将连接从syn queue移到established queue(或accept queue)中并将连接的状态标记为established。最后等待用户空间的进程发起accept()系统调用让内核将其从accept queue中移除。被accept()后的连接表示已经建立好的连接,可以真正实现两端进程间的数据传输。

块设备和字符设备

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/12161.html