• Linux文件操作(一)


    使用文件
    在这一部分当中,我们将会讨论Linux的文件以及目录以及如何来管理他们.我们将会学习创建文件,打开文件,读取文件,写入文件以及关闭文件.我们也将会学习程序如何来管理目录(例如创建,扫描,删除).在上一部分当中我们使用Shell进行编程,而现在我们要开始使用C编程.
    在讨论Linux处理文件I/O之前,我们将会看一些与文件,目录以及设备相关的概念.要处理文件与目录,我们需要使用系统调用(与Windows API相类似的Unix/Linux调用),但是也存在着一系列的库函数,标准I/O库(stdio),来使得我们的文件处理更为有效.
    在这里我们要将讨论处理文件和目录的各种调用,所以我们将会谈到下面的一些内容:
    1 文件与设备
    2 系统调用
    3 库函数
    4 低层文件访问
    5 文件管理
    6 标准I/O库
    7 格式化输入与输出
    8 文件与目录维护
    9 目录扫描
    10 错误
    11 /proc文件系统
    12 高级主题:fcntl与mmap
    Linux文件
    也许有人会问:为什么?我们要来讨论文件结构吗?我们已经知道这些了.不错,与Unix中相类似,文件在Linux环境中是相当重要的,因为他们提供了简单并一致的接口来处理系统服务与设备.在Linux中,一切都是文件.
    这就意味着,在Linux中,程序可以像处理普通文件一样来使用磁盘文件,串口,打印机以及其他的设备.
    而目录,也是一类特殊的文件.在现代的Unix版本中,包括Linux,甚至是超级用户也不可以对他们直接进行写入操作.所有的普通用户使用高层的opendir/readdir接口来读取目录,而不需要知道目录实现的系统细节.我们稍后将会讨论特殊的目录函数.
    确实,在Linux下,所有的内容都被看成文件,或者是通过特殊的文件可以访问.即便这样,也存在着一些主要的原则,而这与我们平常所了解和喜爱的文件是不同的.现在我们来看一下我们谈到的特殊情况.
    目录
    与他的内容一样,一个文件有文件名和一些属性,或者是管理信息;也就是说是这些文件的创建/修改日期与访问权限.这些属性存放在文件的I节点(inode)中,所谓的I节点是文件系统中一个特殊数据块,包含着文件的长度以及在磁盘上的存放位置等信息.系统使用文件的I节点数目,目录结构只是为我们的方便进行文件的命名.
    一个目录就是一个包含I节点数目以及其他文件名的文件.每一个目录实体都是指向一个文件I节点的链接,删除了这个文件名,我们也就删除了这个链接(我们可以使用ls -i命令来查看一个文件的I节点数).使用ln命令,我们可以创建在不同的目录中指向同一个文件的链接.如果指向一个文件的链接数(也就是ls -l命令输出中权限后的数字)为零,那么他所指向的I节点和数据就不再使用并标记为空闲.
    文件排列在目录,而其中一些也许还会有子目录.这样就形成了我们所熟悉的文件系统结构.一个名为neil用户,通常将他的文件存放在home目录中,也许是/home/neil,而在其中也许会存在一些e-mail等一些子目录.在这里我们要注意就是在Unix和Linux Shell中都有一个直接到达我们的用户主目录的命令,也就是~.如果要进入其他用户的目录,我们可以输入~user.正如我们所知道的,每一个用户的home目录都是因为这个特殊的原因而创建的高层目录的子目录,在这里是/home.
    在这里我们要注意的就是在标准的库函数中并不支持文件名参数的~符号.
    /home目录只是根目录/的一个子目录,根目录是文件系统层次的最高层并且在其子目录中包含所有的系统文件.root目录通常包含有系统程序的/bin目录,系统配置文件的/etc目录,系统库的/lib目录.代表物理设备并提供处理这些设备的接口的文件位于/dev目录中.我们可以在Linux File System Stander上查找到更为详细的内容,或者是我们可以使用man hier命令来得到更为详细的描述.
    文件与设备
    甚至是硬件设备也会表示成为文件.例如,作为超级用户,我们可以将CD-ROM挂载作为一个文件:
    # mount -t iso9660 /dev/hdc /mnt/cdrom
    # cd /mnt/cdrom
    这样就会启动CD-ROM设备,并将其当将的内容作为/mnt/cdrom文件结构分枝.我们可以像平常的文件一样进入CD-ROM目录,当然,此时这些内容都是只读的.
    在Unix和Linux中常见了的三个最重要的设备为/dev/console,/dev/tty和/dev/null.
    /dev/console
    这个设备代表系统控制台.错误以及论断信息常会发到这个设备.每一个Unix系统都会有一个指定的终端或是屏幕来接收控制台信息.曾经,这是一个复杂的打印终端.在现代的工作站或是Linux中,他通常是一个活动的虚拟控制台,而在X下,则会是在屏幕上的一个特殊的控制窗口.
    /dev/tty
    /dev/tty是一个进程控制终端的别名,如果有这样的一个进程.例如,由cron运行的进程并不会有一个控制终端,所以他不会打开/dev/tty.
    在他可以使用的地方,/dev/tty允许程序将信息直接写给用户,而不需要考虑用户正在使用何种终端.他在标准输出重定向时相当有用.一个例子就是ls -R | more命令,在这里more可以用输出的每一个新页来提示给用户.
    在这里我们要注意的是,虽然只有一个/dev/console设备,但是却可以通过/dev/tty来访问多个不同的物理设备.
    /dev/null
    这是一个空设备.所有写入这个设备的信息都会被丢弃.当读取这个文件时将会立刻到达文件的结尾处,这样他就可以作为使用cp命令的一个空的文件源.所有不希望看到的输出都可以重定向到这个设备.
    另一个创建新文件的方法就是使用touch <filename>命令,他将修改一个文件的修改日期,而如果这个文件不存在则会创建这个文件.然而他并不会清空他的内容.
    $ echo do not want to see this >/dev/null
    $ cp /dev/null empty_file
    其他可以在/dev目录中找到的设备有硬盘,软盘,通信口,磁带,CD-ROM,声卡以及一些代表系统内部状态的设备.还有一个设备/dev/zero,他可以作为一个空字节的源来创建一个文件大小为零的文件.我们需要超级用户的权限来访问其中的一些设置.普通的用户不可以使用程序来直接访问类似于硬盘这样的低层设备.设备名也许会由系统的不同而不同.在Linux发行版本中通常会有一个需要超级用户来运行的程序来管理这些设备文件,例如mount命令.
    设备可以分为字符设备与块设备.区别就在于一些设备需要一次访问一个块.块设备通常是指那些支持随机存取的文件系统,如硬盘.
    系统调用与设备驱动
    我们可以使用一些函数来访问和控制设备文件.这些函数就是所谓的系统调用,是由Unix/Linux直接提供的到操作系统的接口.
    在操作系统的核心,内核,是大量的设备驱动.这是控制系统硬件的低层接口的集合.例如会有一个磁带驱动,他可以知道如何来启动磁带,向前或是向后,读或是写.他同时知道磁带每次要写入一定大小的数据块.因为磁带是顺序存取的,驱动器不可以直接访问磁带块,而是必须要转到的指定的位置.
    与此相类似,低层硬盘设备也会每一次写入一个指定的磁盘块,但是可以直接访问所需的磁盘块,因为磁盘是随机存取的设备.
    为了提供一个相似的接口,硬盘驱动器封装了所有依赖于硬件的特征.硬件的材质特征我们可以通过ioctl得到.
    /dev中的设备文件可以用同样的方法来使用,他们可以打开,读取,写入和关闭.例如用来打开一个常规文件的open调用可以用来访问一个用户终端,一个打印机或是磁带.
    用来访问设备驱动器的代层系统调用包括:
    1 open:打开一个文件或是设备
    2 read:从一个打开的文件或是设备读
    3 write:写入一个文件或是设备
    4 close:关闭文件或是设备
    5 ioctl:向设备驱动器传递控制信息
    ioctl系统调用用来提供必须的硬件控制,所以他会因设备的不同而不同.例如,ioctl调用可以用来重定位磁带或是设置串口的字符流.正是由于这个原因,ioctl从一个机器到另一个机器并不是必须移植的.另外每一个驱动器定义了自己的ioctl命令集.
    库函数
    直接使用低层的系统来进行输入与输出所存在的问题就是这样的方式并不是十分的有效.为什么呢?
    1 使用系统调用会有一个不好的结果.与函数调用比较起来系统调用要浪费大量的资源,因为这时Linux要在执行我们的程序代码与执行他的内核代码之间进行切换.一个好的方法是尽量使得在一个程序中所用到的系统最少,同时要使得每一个系统调用做尽可能多的工作,例如,每一次要读出或是写入大量的数据,而不是第一次只读写一个字符.
    2 由于硬件的限制会在每一次使用低层系统调用来读写的数据尺寸上有许多的限制.例如,对于磁带驱动器来说,他们每一次可以写入的数据块的大小为10k.所以如果我们每一次要写入的数据块大小并不是10k的整倍数,那么磁带就会向前到下一个10k处,这时就会在磁带上留下一段空白.
    为了提供一个到设备或是硬盘文件的高层接口,与Unix相类似,Linux发行版本提供了大量的标准库.这是一些我们可以包含在我们的程序中用来处理这些问题的函数的集合.一个很好的例子就是提供了缓冲区输出的标准I/O库.我们可以高效的书写变尺寸的数据块,从而可以使用那些为了提供完整的数据块而安排的低层系统调用.这样就会大大的减少了系统调用的开销.
    库函数通常位于手册页的第三部分,而且通常会有一个标准的头文件与之相对,如对于标准输入输出的stdio.h
    低层的文件访问
    每一个正在运行的程序,被称之为一个进程,他们都有一系列的文件描述符与之相对.这是一些我们可以用来访问打开的文件或是设备的小整数.我们可以使用这些迫切描述符中的多少内容是依赖于我们的系统配置的.当启动一个程序时,他通常会有三个已打开的描述符.他们是:
    0:标准输入
    1:标准输出
    2:标准错误输出
    我们可以使用open系统调用来使用其他文件或是设备的描述符,这正是我们稍后将要讨论的问题.然而那些自动打开的文件描述符可以允许我们使用write来创建一些简单的程序.
    write
    write系统会将buf中的第一个nbytes字节的内容写入与fildes文件描述符相关的文件.他的返回值为实际写入的字节数.如果在文件描述符中发生了错误或是底层的设备驱动器要求块的尺寸时,返回值也许会小于nbytes.如果函数返回0,则说明没有数据写入.如果函数返回-1,则说明在write调用中发生了错误,而这个错误将会存放在errno全局变量中.
    write的语法如下:
    #include <unistd.h>
    size_t write(int fildes, const void *buf, size_t nbytes);
    有了这些知识,我们可以写出我们的第一个程序,simple_write.c:
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
        if ((write(1, “Here is some data/n”, 18)) != 18)
            write(2, “A write error has occurred on file descriptor 1/n”,46);
        exit(0);
    }
    这个程序只是简单的在标准输出上打印一条信息.当程序结束时,所有打开的描述符都会自动关闭,所以我们并不需要显示的关闭他们.然而当我们处理缓冲区的输出时并不是这样的情况.
    运行这个程序,我们会得到下面的输出结果:
    $ simple_write
    Here is some data
    $
    在这里有一点值得我们注意的就是也许会报告他所写入的字节比我们所要求的要少.这并不是一个必须的错误.在我们的程序中,我们需要松果检查error从而检测错误并且调用write写入其余的数据.
    read
    read系统调用会从fildes文件描述符所指的文件中读取nbytes字节的数据并将所读取的数据放在数据区域buf中.他的返回值为实际读取的字节数,这也许会比所要求的数值要小.如果一个read函数返回0,则并没有读取任何内容,而是到达了文件的结尾.同样,如果发生错误则返回-1.
    其语法如下:
    #include <unistd.h>
    size_t read(int fildes, void *buf, size_t nbytes);
    下面的这个简单的程序simple_read.c,将会从标准输入读取128个字符到标准输出.如果实际的数据个数小于128,则会读取全部的内容.
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
        char buffer[128];
        int nread;
        nread = read(0, buffer, 128);
        if (nread == -1)
            write(2, “A read error has occurred/n”, 26);
        if ((write(1,buffer,nread)) != nread)
            write(2, “A write error has occurred/n”,27);
        exit(0);
    }
    如果我们运行这个程序,我们会得到下面的输出结果:
    $ echo hello there | simple_read
    hello there
    $ simple_read < draft1.txt
    Files
    In this chapter we will be looking at files and directories and how to manipulate
    them. We will learn how to create files, o$
    在第一次运行时,我们为我们的程序使用echo命令创建一些输入,这些输入将导入我们的程序.在第二次运行时,我们从一个文件重定向输入.在这种情况下,我们发现文件draft1.txt的第一部分出现在标准输出中.
    open
    要创建一个新的文件描述符,我们需要使用open系统调用.其语法格式如下:
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    int open(const char *path, int oflags);
    int open(const char *path, int oflags, mode_t mode);
    更为严格的说,我们并不需要包含sys/types.h和sys/stat.h来打开一个POSIX系统,但是在一些Linux系统这却是必须的.
    从简单的角度来说,open建立一个到文件或是设备的访问路径.如果调用成功,则会返回一个文件描述符,而这个文件描述符可以用于read,write或是其他的系统调用.这个文件描述符是唯一,而不会与其他正在运行的进程所共享.如果两个程序在同一时间打开了同一个,他们就会分别维护不同的文件描述符.如果他们同时写入文件,他们将会在他们所读入的地方写入文件.这些数据并不会插入,而是一个会覆盖另一个.每一个程序都会记录一个他们所读入或是写入文件的偏移量的信息.我们可以通过使用文件加锁的方法来避免这样的情况发生.
    要打开的文件或是设备的名字是以path参数的形式传入的.而oflags参数则是指定了要在打开的文件上进行的动作.
    oflags是以命令(mandatory)文件访问或是其他一些可选模式组合的方式来指定的.open调用必须指定下列文件访问模式中的一种:
    O_RDONLY    以只读方式打开
    O_WRONLY    以只写方式打开
    O_RDWR        以读写方式打开
    这个调用还可以在oflags参数中包含下列可选模式的组合(使用OR):
    O_APPEND    在文件末尾写入数据
    O_TRUNC        将文件的大小设为零,而不管存在的内容
    O_CREAT        如果需要,以指定的模式创建文件
    O_EXCL        与O_CREAT配合使用,保证调用创建文件.
    open是原子型的,也就是他只作为一个函数调用.工程禁止两个程序同时创建文件.如果文件已经存在,则open失败.
    其他一些oflags的可能的参数可以在open手册页中找到,这个手册页可在手册页的第二部分找到(使用man 2 open).
    open如果调用成功则会返回一个新的文件描述符(通常是一个非负整数),如果失败则会返回-1,同时open会设置errno全局变量来指明失败的原因.我们将会在以后的部分中详细的讨论errno.新的文件描述符总是最小的未被使用的文件描述符,这在一些环境下是相当有用的.例如,如果一个程序关闭了他的标准输出,然后调用open函数,文件描述符1可以重新使用,而标准输出也可以高效的重定向到另一个不同的文件或是设备.还有一个被POSIX标准化的creat调用,但是这个函数并不常用.creat并不如我们所希望那样的仅是创建文件,而是会同时打开这个文件,这与使用open函数同时使用O_CREAT|O_WRONLY|O_TRUNC oflags参数的效果是一样的.
    初始权限
    当我们使用open函数的O_CREAT来创建一个文件时,我们必须使用第三个参数的形式.mode是第三个参数,他是由文件sys/stat中所定义的.这些权限如下:
    S_IRUSR:拥有者读权限
    S_IWUSR:拥有者写权限
    S_IXUSR:拥有者执行权限
    S_IRGRP:组读权限
    S_IWGRP:组写权限
    S_IXGRP:组执行权限
    S_IROTH:其他用户读权限
    S_IWOTH:其他用户写权限
    S_IXOTH:其他用户执行权限
    例如下面的例子:
    open (“myfile”, O_CREAT, S_IRUSR|S_IXOTH);
    这个例子将会生成一个名为myfile的文件,其权限为拥有者的读权限和其他用户的执行权限,而且只有这些权限.
    $ ls -ls myfile
    0 -r------x   1 neil software 0 Sep 22 08:11 myfile*
    会有许多的因素影响文件的权限.首先,只有文件在创建时使用的权限.第二,用户的屏蔽位(由umask命令所指定)影响已创建的文件权限.open调用时所指定的模式值以及运行时所保留的用户屏蔽位.例如,如果用户的屏蔽位设置为001并且在创建文件时指定了S_IXOTH权限,则所创建的文件并不会有其他用户的执行权限,因为用户的屏蔽位并没有提供其他用户的执行权限.事实上,open以及creat调用中的标志需要请求设置权限.所请求设置的权限有没有设置则依赖于运行时的umask值.
    umask
    umask是一个系统变量,当创建一个文件时,可以被用来为一个文件的权限设置屏蔽位.我们可以通过执行umask命令并提供一个新的值从而可以改变这个变量的值.umask的值是一个三位的十六进制数.每一个数字是1,2或4中的数相加的结果值.我们可以从下表中清楚地明白这个意思.每一个不同的数字位可以对应user,group,other的权限.
    第一位:
    0    没有用户的权限被禁止
    4    用户读权限被禁止
    2    用户写权限被禁止
    1    用户执行权限被禁止
    第二位:
    0    没有组权限被禁止
    4    组读权限被禁止
    2    组写权限被禁止
    1    组执行权限被禁止
    第三位:
    0    没有其他用户的权限被禁止
    4    其他用户读权限被禁止
    2    其他用户写权限被禁止
    1    其他用户执行权限被禁止
    例如,要禁止group的写与执行权限以及other的写权限,我们可以使用下面的umask值:
    Digit Value
    1     0
    2     2
          1
    3     2
    每一位的值是相加的结果,所以第二位将是2&1,也就是3.所以umask值将为032.
    当我们使用open或是creat来创建一个文件时,mode参数将会与umask值相比较.同时在mode参数和umask值中进行了设置的位将会被移除.最终的结果将会是用户可以设置他们的环境来说:不要创建带有其他用户写权限的文件,仅管创建文件的程序要求这样的权限设置.这样并不会阻止一个程序或是用户在以后使用chmod命令(或是在一个程序中使用chmod系统调用)来增加其他用户的写权限,但是这确实可以避免用户在所有的新文件上检查和设置权限,从而可以起到保护用户的作用.
    close
    我们使用close系统调用来关闭一个文件描述符,files与其相对应的文件之间的关联.这样这个文件描述符就可以重新被使用.如果调用成功则会返回0,错误返回-1.
    其语法格式如下:
    #include <unistd.h>
    int close(int fildes);
    在这里我们要注意就是检查close的返回值是相当重要的.一些文件系统尤其是网络文件系统,当写入文件时并不会报告写入错误,直到文件关闭,从而会造成当执行写入动作并没有真正将数据写入文件.
    一个运行的程序同时打开的文件数目是有限制的.这个限制是由limits.h中定义的OPEN_MAX值决定的,会因系统的不同而不同,但是POSIX要求最少为16.这个限制也许会受到本地系统限制的影响.
    ioctl
    ioctl是一个事物的集合.他提供了一个接口从而可以控制设备的形为以及他们的描述符和服务的配置.终端,文件描述符,套接字,甚至磁带都有为他们所定义的ioctl调用,我们可以查看相关的手册页得到更为详细的内容.POSIX只为流定义了ioctl.其语法格式如下:
    #include <unistd.h>
    int ioctl(int fildes, int cmd, ...);
    ioctl会执行由文件描述符fildes所对应目标上的cmd所指示的函数.他也许会带有第三个可选的参数,这要依赖于具体的设备所提供的函数.
    现在我们已经了解了足够多的关于open,read和write系统调用的知识,我们现在可以写一个低层程序,copy_system.c,将一个文件的内容一个字符一个字符地拷贝到另一个文件.
    (在整个这个讨论中,我们将使用不同的方法来写这个程序,从而可以比较不同方法之间的效率问题.为简单起见,我们假设输入文件存在,而输出文件不存在,并且所有的读与写操作都是成功的.当然,在真正的程序设计中,我们将会检测这些假设是否真实存在).
    #include  <unistd.h>
    #include  <sys/stat.h>
    #include  <fcntl.h>
    #include  <stdlib.h>
    int main()
    {
        char c;
        int in, out;
        in = open(“file.in”, O_RDONLY);
        out = open(“file.out”, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
        while(read(in,&c,1) == 1)
             write(out,&c,1);
        exit(0);
    }
    在这里我们要注意的是,#include <unistd.h>必须放在第一行,因为他定义POSIX编译相关的标记,从而会影响其他包含进来的文件.
    首先,我们需要创建一个测试输入文件,大小为1Mb,命名为file.in.
    如果我们运行这个程序,我们会得到下面的输出:
    $ TIMEFORMAT=”” time copy_system
    4.67user 146.90system 2:32.57elapsed 99%CPU
    ...
    $ ls -ls file.in file.out
    1029 -rw-r--r-   1 neil     users     1048576 Sep 17 10:46 file.in
    1029 -rw-----   1 neil     users     1048576 Sep 17 10:51 file.out
    在这里我们使用time程序来测量运行这个程序所需要的时间.TIMEFORMAT变量是在Linux系统用来覆盖POSIX默认的时间输出格式的,而在默认的情况下并不会包含CPU的使用情况.从这个输出结果我们可以看出在这个较老的系统上,1Mb的文件file.in成功地复制到了文件file.out,而这个输出输出文件只对于拥者本身有读和写的权限.然而这个程序的运行却使用了2分半,并且几乎占用全部的CPU运行时间.他这样慢的原因是因为他使用了2百万次的系统调用.
    在近些年,Linux在系统调用与文件系统方面取得了较大的进步.比较而言,在2.4的内核上进行测试则只需要不到5秒的时间.
    $ TIMEFORMAT=”” time copy_system
    1.07user 3.50system 0:04.77elapsed 95%CPU
    ...
    我们可以通过一次复制较大的块来进行改进.在下面的这个改进的程序copy_block.c中,一次拷贝1k字节,并且也使用系统调用:
    #include  <unistd.h>
    #include  <sys/stat.h>
    #include  <fcntl.h>
    #include  <stdlib.h>
    int main()
    {
        char block[1024];
        int in, out;
        int nread;
        in = open(“file.in”, O_RDONLY);
        out = open(“file.out”, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
        while((nread = read(in,block,sizeof(block))) > 0)
             write(out,block,nread);
        exit(0);
    }
    现在我们来运行这个程序,首先要删除旧的输出文件:
    $ rm file.out
    $ TIMEFORMAT=”” time copy_block
    0.00user 0.01system 0:00.01elapsed 62%CPU
    ...
    现在这个程序只需要百分之一秒,而且他只需要大概2000次系统调用.当然,这个时间会因系统的不同而异,但是他们却显示出系统调用的大量开支,所以他们的使用是值得优化的.
    管理文件的其他系统调用
    还有许多其他的系统调用可以用来处理这样的低层文件描述符.这些允许一个程序来控制如何来使用一个并且返回状态信息.
    lseek
    lseek系统调用可以用来设置文件描述符fildes的读或是写的指针.也就是说我们可以用来设置将会发生读写的位置.我们可以在文件中使用绝对地址来设置文件指针或是与当前位置或是文件结尾的相对位置来设置文件指针.
    其语法格式如下:
    #include <unistd.h>
    #include <sys/types.h>
    off_t lseek(int fildes, off_t offset, int whence);
    offset参数用来指定位置,而whence参数用来指定如何使用偏移量.whence的取值可以是下列中的一个:
    SEEK_SET:offset是一个绝对地址
    SEEK_CUR:offset是一个对当前位置的相对地址
    SEEK_END:offset是对文件结尾的相对位置
    lseek的返回值为从设置了文件指针的文件开始以字节大小度量的offset值,如果失败则会返回-1.在lseek操作中使用的off_t类型是在sys/types.h中定义的独立实现.
    fstat,stat,lstat
    fstat系统调用会返回与打开文件描述符相对应的文件的状态信息.这些信息将会写入一个结构buf中,而buf的地址是作为参数进行传递的.
    他们的语法格式如下:
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    int fstat(int fildes, struct stat *buf);
    int stat(const char *path, struct stat *buf);
    int lstat(const char *path, struct stat *buf);
    在这里我们所包含的sys/types.h文件是可选的,,但是我们建议在使用系统调用时这样做,因为他们的一些定义采用了标准类型的别名,而这些是有可能发生变化的.
    相关的函数stat与lstat将返回一个命名文件的状态信息.他们会产生同样的结果,但是当文件是一个符号链接时就会有一些不同.lstat会返回这个链接的信息,而stat却会返回链接所指向的文件的信息.
    stat结构的成员会因系统的不同的而有所不同的,但是一般情况下都会有下面的一些数据:
    st_mode        文件权限以及文件类型信息
    st_ino        文件I节点信息
    st_dev        文件所在的设备信息
    st_uid        文件所有者的用户标识
    st_gid        文件所有者的组标识
    st_atime    上一次访问的时间
    st_ctime    上一次对权限,所有者,组,或是内容的修改时间
    st_mtime    上一次对内容的修改时间
    st_nlink    到这个文件的硬链接数目
    由stat结构返回的st_mode同时也会包含一些在头文件sys/stat.h中定义的许多相关的宏.这些宏包括权限名字,标记文件类型,以用用不帮助测试一些特殊的类型和权限的内容.
    权限标记与我们在前面所说到的open系统调用的权限相同.文件类型标记包括下面的一些:
    S_IFBLK:实体是一个特殊的块设备
    S_IFDIR:实体是一个目录
    S_IFCHR:实体是一个特殊的字符设备
    S_IFIFO:实体是一个IFIFO(命令管道)
    S_IFREG:实体是一个常规文件
    S_IFLNK:实体是一个符号链接
    其他的一些模式标记如下:
    S_ISUID:实体在执行权限上设置了UID
    S_ISGID:实体在执行权限上设置了GID
    用来解释st_mode的屏蔽(mask)如下:
    S_IFMT:文件类型
    S_IRWXU:用户读/写/执行权限
    S_IRWXG:组读/写/执行权限
    S_IRWXO:其他用户读/写/执行权限
    还有一些定义的宏用来帮助我们确定文件类型.这些只是将合适的屏蔽模式标记与合适的类型标记进行对比.他们包含:
    S_ISBLK:为特殊的块文件测试
    S_ISCHR:为特殊的字符文件测试
    S_ISDIR:为目录测试
    S_ISFIFO:为FIFO测试
    S_ISREG:为常规文件测试
    S_ISLNK:为符号链接测试
    例如,要测试一个不是一个目录并且为所有者设置了执行权限,但是并没有其他用户的权限,我们可以用下面的测试:
    struct stat statbuf;
    mode_t modes;
    stat(“filename”,&statbuf);
    modes = statbuf.st_mode;
    if(!S_ISDIR(modes) && (modes & S_IRWXU) == S_IXUSR)
        ...
    dup与dup2
    dup系统调用提供了一个文件描述符副本的方式,可以提供给我们两个或是更多个访问同一个文件的描述符.这可以用于在一个文件读和写不同的位置.dup系统产生了文件描述fildes的一个副本,返回一个新的描述符.dup2系统可以通过指定拷贝用的描述符可以高效的将一个文件描述符拷贝到另一个.
    其语法格式如下:
    #include <unistd.h>
    int dup(int fildes);
    int dup2(int fildes, int fildes2);
    这些调用在我们用管道进行多个进程的通信时就会非常有用.我们将会在以后的部分更为详细的讨论dup系统.
  • 相关阅读:
    Easyui使用记录
    Ubuntu 设置UFW防火墙
    MySQL 官方文档
    MySQL 版本
    MySQL主从架构之Master-Master互为主备
    MySQL主从架构之Master-Slave-Slave级联
    MySQL主从架构之Master-Slave主从同步
    Linux crond实例
    Ubuntu su: Authentication failure
    MySQL基准测试
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2462005.html
Copyright © 2020-2023  润新知