linux 编程显示文件大小

2015-08-21 08:15:21 baidu_28312631 阅读数 3501

   Linux文件 I\O 介绍

    1. Linux系统调用

    Linux系统调用(system call)是指操作系统提供给用户程序的一组“特殊接口”,用户程序可以通过这组“特殊”接口来获得操作系统提供的特殊服务。

    为了更好的保护内核空间,将程序的运行空间分为内核空间和用户空间,他们运行在不同的级别上,在逻辑上是相互隔离的。在Linux中,用户程序不能直接访问内核提供的服务,必须通过系统调用来使用内核提供的服务。

    Linux中的用户编程接口(API)遵循了UNIX中最流行的应用编程界面标准——POSIX。这些系统调用编程接口主要是通过C库(libc)实现的。

    2. 文件描述符

    对内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当写一个文件时,用open或create返回的文件描述符标识该文件,将其作为参数传送给read或write。

    在POSIX应用程序中,整数0、1、2应被代换成符号常数:

  1.     STDIN_FILENO(标准输入,默认是键盘)
  2.     STDOUT_FILENO(标准输出,默认是屏幕)
  3.     STDERR_FILENO(标准错误输出,默认是屏幕)

    这些常数都定义在头文件<unistd.h>中,文件描述符的范围是0~OPEN_MAX。早期的UNIX版本采用的上限值是19(允许每个进程打开20个文件), 现在很多系统则将其增加至256。

    可用的文件I\O函数很多,包括:打开文件,读文件,写文件等。大多数Linux文件I\O只需要用到5个函数:open,read,write,lseek以及close。


   基本API学习

    1. open

    需要包含的头文件:<sys/types.h>,<sys/stat.h>,<fcntl.h> 函数原型:  

int open(const str * pathname, int oflag, [..., mode_t mode])

    功能:打开文件  返回值:成功则返回文件描述符,出错返回-1  参数:

    pathname: 打开或创建的文件的全路径名   oflag:可用来说明此函数的多个选择项, 详见后。 mode:对于open函数而言,仅当创建新文件时才使用第三个参数,表示新建文件的权限设置。

    详解oflag参数:oflag 参数由O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)中的一个于下列一个或多个常数 O_APPEND: 追加到文件尾 O_CREAT: 若文件不存在则创建它。使用此选择项时,需同时说明第三个参数mode,用其说明新闻件的访问权限 O_EXCL: 如果同时指定O_CREAT,而该文件又是存在的,报错;也可以测试一个文件是否存在,不存在则创建。O_TRUNC: 如果次文件存在,而且为读写或只写成功打开,则将其长度截短为0 O_SYNC: 使每次write都等到物理I\O操作完成。

    用open创建一个文件:open.c 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FILE_PATH   "./test.txt"

int main(void)
{
    int fd;
    if ((fd = open(FILE_PATH, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
        printf("open error\n");
        exit(-1);
    } else {
        printf("open success\n");
    }
    return 0;
}

    如果当前目录下以存在test.txt,屏幕上就会打印“open error”;不存在则创建该文件,并打印“open success”。

    2. read

    需要包含的头文件:<unistd.h>

    函数原型:

ssize_t read(int fd, void * buf, size_t count)

    功能:从打开的文件中读取数据。  返回值:实际读到的字节数;已读到文件尾返回0,出错的话返回-1,ssize_t是系统头文件中用typedef定义的数据类型相当于signed int 参数:fd:要读取的文件的描述符buf:得到的数据在内存中的位置的首地址count:期望本次能读取到的最大字节数。size_t是系统头文件中用typedef定义的数据类型,相当于unsigned int。

    3. write

   需要包含的头文件:<unistd.h> 函数原型:

ssize_t write(int fd, const void * buf, size_t count)

    功能:向打开的文件写数据 返回值:写入成功返回实际写入的字节数,出错返回-1。

    不得不提的是,返回-1的常见原因是:磁盘空间已满,超过了一个给定进程的文件长度。

    参数: fd:要写入文件的文件描述符buf:要写入文件的数据在内存中存放位置的首地址count:期望写入的数据的最大字节数。

    read和write使用范例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    char buf[100];
    int num = 0;

    // 获取键盘输入,还记得POSIX的文件描述符吗?
    if ((num = read(STDIN_FILENO, buf, 10)) == -1) {
        printf ("read error");
        error(-1);
    } else {
    // 将键盘输入又输出到屏幕上
        write(STDOUT_FILENO, buf, num);
    }

    return 0;
}

    4. close

    需要包含的头文件:<unistd.h> 函数原型:int close(int filedes) 功能:关闭一个打开的文件 参数:需要关闭文件的文件描述符。

    当一个进程终止的时候,它所有的打开文件都是由内核自动关闭。很多程序都使用这一功能而不显式地调close关闭一个已打开的文件。 但是,作为一名优秀的程序员,应该显式的调用close来关闭已不再使用的文件。

    5. lseek

    每个打开的文件都有一个“当前文件偏移量”,是一个非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都是从当前文件偏移量处开始,并使偏移量增加所读或写的字节数。默认情况下,你打开一个文件(open),除非指定O_APPEND参数,不然位移量被设为0。

    需要包含的头文件:<sys/types.h>,<unistd.h> 函数原型:

off_t lseek(int filesdes, off_t offset, int whence)

    功能:设置文件内容读写位置 返回值:成功返回新的文件位移,出错返回-1;同样off_t是系统头文件定义的数据类型,相当于signed int 参数:

  1.     whence是SEEK_SET, 那么该文件的位移量设置为据文件开始处offset个字节
  2.     whence是SEEK_CUR, 那么该文件的位移量设置为当前值加offset。offset可为正或负
  3.     whence是SEEK_END, 那么该文件的位移量设置为文件长度加offset。offset可为正或负
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char * argv[])
{
    int fd;
    char buf[100];
    if ((fd = open(argv[1], O_RDONLY)) < 0) {
        perror("open");
        exit(-1);
    }
    read(fd, buf, 1);
    write(STDOUT_FILENO, buf, 1);
    lseek(fd, 2, SEEK_CUR);

    read(fd, buf, 1);
    write(STDOUT_FILENO, buf, 1);
    lseek(fd, -1, SEEK_END);

    read(fd, buf, 1);
    write(STDOUT_FILENO, buf, 1);
    lseek(fd, 0, SEEK_SET);

    read(fd, buf, 1);
    write(STDOUT_FILENO, buf, 1);
    close(fd);
    printf("\n");

    return 0;
}

    6. select

    之前的read函数可以监控一个文件描述符(eg:键盘)是否有输入,当键盘没有输入,read将会阻塞,直到用户从键盘输入为止。用相同的方法可以监控鼠标是否有输入。但想同时监控鼠标和键盘是否有输入,这个方法就不行的了。

// /dev/input/mice 是鼠标的设备文件
fd = open("/dev/input/mice", O_RDONLY);
read(0, buf, 100);
read(fd, buf, 100);

    在上面的程序中,当read键盘的时候,若无键盘输入则程序阻塞在第2行,此时即使鼠标有输入,程序也没有机会执行第3行获得鼠标的输入。这种情况就需要select同时监控多个文件描述符。

    需要包含的头文件:<sys/select.h> 函数原型:

int select(int maxfd, fd_set \* readset, fd_set \* writeset, fd_set \* exceptset, const struct timeval \* timeout)

    返回值:失败返回-1,成功返回readset,writeset,exceptset中所有,有指定变化的文件描述符的数目(若超时返回0)

    参数: maxfd:要检测的描述符个数, 因此值应为最大描述符+1 readset:被监控是否有输入的文件描述符集。不监控时,设为NULL writeset:被监控是否可以输入的文件描述符集。不监控时,设为NULL exceptset:被监控是否有错误产生的文件描述符集。不监控时,设为NULL timeval:监控超时时间。设置为NULL表示一直阻塞到有文件描述符被监控到有指定变化。

    readset,writeset,exceptset这三个描述符集指针均是值—结果参数,调用的时候,被监控描述符相应位需要置1;返回时,未就绪的描数字相应位会被清0,而就绪的会被置1。下面的系统定义的宏,和select配套使用

    FD_ZERO(&rset):将文件描述符集rset的所有位清0

    FD_SET(4, &reset):设置文件描述符集rset的bit 4

    FD_CLR(fileno(stdin), &rset):将文件描述符集rset的bit 0清0

    FD_ISSET(socketfd, &rset):若文件描述符集rset中的socketfd位置1   

#include <stdio.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>

#define MAXNUM      100
#define OPEN_DEV    "/dev/input/mice"

int main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval, fd;
    char buf[MAXNUM];

    fd = open(OPEN_DEV, O_RDONLY);
    while (1) {
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        FD_SET(fd, &rfds);
        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(fd+1, &rfds, NULL, NULL, &tv);
        if (retval < 0)
            printf ("error\n");
        if (retval == 0)
            printf ("No data within 5 seconds\n");
        if (retval > 0) {
            if (FD_ISSET(0, &rfds)) {
                printf ("Data is available from keyboard now\n");
                read(0, buf, MAXNUM);
            }
            if (FD_ISSET(fd, &rfds)) {
                printf ("Data is available from mouse now\n");
                read(fd, buf, MAXNUM);
            }
        }
    }
    return 0;
}

   

   stat 的使用

    Linux有个命令,ls -l,效果如下:

   

    这个命令能显示文件的类型、操作权限、硬链接数量、属主、所属组、大小、修改时间、文件名。它是怎么获得这些信息的呢,请看下面的讲解。

    1. stat 的基本使用

    系统调用stat的作用是获取文件的各个属性。

    需要包含的头文件: <sys/types.h><sys/stat.h><unistd.h> 函数原型:   

int stat(const char \* path, struct stat \* buf)

     功能:查看文件或目录属性。将参数path所指的文件的属性,复制到参数buf所指的结构中。参数:path:要查看属性的文件或目录的全路径名称。buf:指向用于存放属性的结构体。stat成功调用后,buf的各个字段将存放各个属性。struct stat是系统头文件中定义的结构体,定义如下: 

struct stat {
    dev_t       st_dev;
    ino_t       st_ino;
    mode_t      st_mode;
    nlink_t     st_nlink;
    uid_t       st_uid;
    gid_t       st_gid;
    dev_t       st_rdev;
    off_t       st_size;
    blksize_t   st_blksize;
    blkcnt_t    st_blocks;
    time_t      st_atime;
    time_t      st_mtime;
    time_t      st_ctime;
};

    st_ino:节点号 st_mode:文件类型和文件访问权限被编码在该字段中 st_nlink:硬连接数  st_uid:属主的用户ID  st_gid:所属组的组ID st_rdev:设备文件的主、次设备号编码在该字段中 st_size:文件的大小  st_mtime:文件最后被修改时间

    返回值:成功返回0;失败返回-1

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    struct stat     buf;
    if(argc != 2) { 
        printf("Usage: stat <pathname>"); 
        exit(-1); 
    }
    if(stat(argv[1], &buf) != 0) { 
        printf("stat error."); 
        exit(-1); 
    }
    printf("#i-node:    %ld\n", buf.st_ino);
    printf("#link:      %d\n", buf.st_nlink);
    printf("UID:        %d\n", buf.st_uid);
    printf("GID:        %d\n", buf.st_gid);
    printf("Size        %ld\n", buf.st_size);
    exit(0);
}

     2. 文件类型的判定

    struct stat中有个字段为st_mode,可用来获取文件类型和文件访问权限,我们将陆续学到从该字段解码我们需要的文件信息。st_mode中文件类型宏定义

   

    我们修改上面的例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    struct stat buf;
    char * file_mode;
    if(argc != 2) {
        printf("Usage: stat <pathname>\n"); 
        exit(-1); 
    }
    if(stat(argv[1], &buf) != 0) {
        printf("stat error.\n"); 
        exit(-1); 
    }
    if (S_ISREG(buf.st_mode))
        file_mode = "-";
    else if (S_ISDIR(buf.st_mode))
        file_mode = "d";
    else if (S_ISCHR(buf.st_mode))
        file_mode = "c";
    else if(S_ISBLK(buf.st_mode))
        file_mode = "b";
    printf("#i-node:    %ld\n", buf.st_ino);
    printf("#link:      %d\n", buf.st_nlink);
    printf("UID:        %d\n", buf.st_uid);
    printf("GID:        %d\n", buf.st_gid);
    printf("Size        %ld\n", buf.st_size);
    printf("mode: %s\n", file_mode);
    exit(0);
}

     

    目录操作

    当目标是目录而不是文件的时候,ls -l的结果会显示目录下所有子条目的信息,怎么去遍历整个目录呢?答案马上揭晓!

    1. 打开目录

    需要包含的头文件:<sys/types.h><dirent.h>

    函数原型:DIR * opendir(const char * name)

    功能:opendir()用来打开参数name指定的目录,并返回DIR *形态的目录流 返回值:成功返回目录流;失败返回NULL

    2. 读取目录

    函数原型:struct dirent * readdir(DIR * dir)

    功能:readdir()返回参数dir目录流的下一个子条目(子目录或子文件)

    返回值: 成功返回结构体指向的指针,错误或以读完目录,返回NULL

    函数执行成功返回的结构体原型如下:  

struct dirent {
   ino_t   d_ino;
   off_t   d_off;
   unsigned short  d_reclen;
   unsigned char   d_type;
   char    d_name[256];
};

    其中 d_name字段,是存放子条目的名称

    3. 关闭目录

    函数原型:int closedir(DIR * dir)

    功能:closedir()关闭dir所指的目录流

    返回值:成功返回0;失败返回-1,错误原因在errno中

    我们来学习一个综合的例子吧: 

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
    DIR *dp;
    struct dirent *entp;
    if (argc != 2) {
        printf("usage: showdir dirname\n");
        exit(0);
    }
    if ((dp = opendir(argv[1])) == NULL) {
        perror("opendir");
        exit(-1);
    }
    while ((entp = readdir(dp)) != NULL)
        printf("%s\n", entp->d_name);

    closedir(dp);
    return 0;
}
2018-06-03 20:45:04 asmartkiller 阅读数 9632

linux 编程入门

Linux/UNIX系统编程手册 【德】Michael Kerrisk著 着眼于Linux 2.6.x和GNU C语言库(glibc)版本2

Web站点  http://man7.org/tlpi  勘误 http://man7.org/tlpi/errata

为调试程序,或是研究程序的运作机制,可使用附录A所介绍的strace命令,对程序发起的系统调用进行跟踪

一般情况下,会将Linux内核可执行文件命名为/boot/vmlinuz或与之类似的路径名。早期的UNIX实现称其内核为UNIX,后续实现虚拟内存机制的UNIX系统中,其内核名称变更为vmunix。对Linux来说,文件名称中的系统名需要调整,以z替换linux末尾的x,意在表明内核是经过压缩的可执行文件。

系统的每个用户都拥有唯一的登录名和与之对应的整数型用户ID(UID)。系统密码文件/etc/password为每个用户都定义有一行记录,除了上述两项信息外,该记录还包含如下信息:组ID(用户所属第一个组的整数型组ID),主目录(用户登录后所居于的初始目录),登录shell(执行以解释用户命令的程序名称)。

一个用户可同时属于多个组,每个用户组对应着系统组文件/etc/group中的一行记录:组名(唯一的组名称),组ID(GID,与组相关的整数型ID),用户列表(隶属于该组的用户登录列表,通过上述密码文件记录的group ID字段未能标识出的该组其他成员,也在此列)

超级用户账号的用户ID为0,通常登录名为root。在一般的UNIX系统上,超级用户都可以凌驾于系统权限检查之上。无论对文件施以何种访问权限限制,超级用户都可以访问系统中的任何文件,也能发送信号干预系统运行的所有用户进程。

 

 

目录是一种特殊型的文件,内容采用表格形式,数据项包含文件名以及对相应文件的引用(文件名+i-node编号)。文件名+引用的组合称为链接。每个文件都可以有多条链接,因而可以有多个名称,在相同或不同的目录中出现。目录可包含指向文件或其他目录的链接。每个目录至少包含两条记录:.(指向目录自身的链接), ..(指向其上级目录的链接)。对于根目录而言,..是指向根目录自身的链接(/..等于/)

每个进程都有两个目录相关属性根目录及当前工作目录,分别用于为解释绝对路径名和相对路径名提供参照点。虽然一个进程能够打开一个目录,但却不能使用read()去读取目录的内容,也不能使用write()来改变一个目录的内容。若目录条目的i-node字段值为0,则表明该条目尚未使用。

当前工作目录:每个进程都有一个当前目录,是进程解释相对路径名的参照点。进程的当前目录继承自其父进程。可针对目录进行权限设置,读权限允许列出目录内容(即该目录下的文件名),写权限允许对目录内容进行修改(比如:添加,修改或删除文件名),执行权限允许对目录中的文件进行访问(但需受文件自身访问权限的约束)。

链接(硬链接)

文件i-node的存储信息列表,并未包含文件名,而通过目录列表内的一个映射来定义文件名称。(能够在相同或者不同目录中创建多个名称,每个均相同的i-node节点)。硬链接为目录中的一条记录:文件名+i-node编号。

打印索引或者大家俗称的inode号,我们可以使用-i选项,即“ls -li”(索引号会显示在第一列)。第三列显示指向该i-node的链接计数。执行ln abc xyz后,abc所指向i-node的链接计数升至2。名称abc和xyz均指向相同的i-node条目。

rm命令从目录列表中删除一文件,将相应i-node的链接计数减一,若链接计数因此而将为0,则还将释放该文件名所指代的i-node和数据块。同一文件的所有名字(链接)地位平等,在移除与文件相关的第一个名称后,物理文件继续存在,但只能通过另一文件名来访问其内容。

同级目录下不能创建同名硬链接

因为目录条目(硬链接)对文件的指代采用i-node编号,而i-node编号的唯一性仅在一个文件系统之内才能得到保障,所以硬链接必须与指代的文件驻留在同一文件系统中。不能为目录创建硬链接,从而避免出现令诸多程序陷入混乱的链接环路。

符号链接  (软链接)

类似于普通链接,符号链接给文件起一个别号。在目录列表中,普通链接是内容的“文件名+指针”的一条记录,而符号链接则是经过特殊标记的文件,内容包含了另一文件的名称。符号链接也是目录中的一条记录(文件名+i-node编号),不过其对应的i-node节点的数据块所指向的内容为该符号链接所指代的文件的文件名(可以是绝对路径,也可以是相对路径,解释相对符号链接时以链接本身的位置作为参照点)。

符号链接是由ln -s命令创建,ls -F命令的输出结果中会在符号链接的尾部标记@。文件的链接计数中并未将符号链接计算在内。如果移除符号链接所指向的文件名,符号链接本身还将继续存在,尽管无法再对其进行解引用(下溯)操作,将此类链接称为悬空链接。更有甚者,还可以为并不存在的文件名创建一个符号链接。因为符号链接指代一个文件名,而非i-node编号,所以可以用其来链接不同文件系统中的一个文件,且可以为目录创建符号链接。(工具命令有能力识别硬软链接,默认情况下不对软链接进行解引用,避免因使用软链接而陷入引用环路)

 

3种标准文件描述符(标准输入,标准输出,标准错误),在程序开始运行之前,shell代表程序打开这3个文件描述符,程序继承了shell文件描述符的副本。(在交互式shell中,这3个文件描述符通常指向shell运行所在终端)如果命令行指定对输入/输出进行重定向操作,那么shell会对文件描述符做适当的修改,然后再启动程序。

虽然stdin,stdout,stderr变量在程序初始化时用于指代其他进程的标准输入,标准输出,标准错误(按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联),但是调用freopen()库函数可以使这些变量指代其他任何文件对象。(作为其操作的一部分,freopen()可以在将流(stream)重新打开之际一并更换隐匿其中的文件描述符。比如:针对stdout调用freopen()后,无法保证stdout变量值仍然为文件描述符1)如果关闭了STDIN_FILENO(即关闭了文件描述符0),如果使用open()打开其他文件,由于文件描述符0未用,open()调用势必使用此描述符来打开文件。

 

获取文件信息:stat()

#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);

系统调用stat()和lstat()无需对其所操作的文件拥有任何权限,但针对指定pathname的父母录要有执行(搜索)权限。

与常量S_IFMT相与,可从该字段中析取文件类型(Linux使用st_mode字段中的4位来标识文件类型位)。

使用utime()和utimes()改变文件时间戳

使用utime()或与之相关的系统调用集之一,可显式改变存储于文件i节点中的文件上次访问时间戳和上次修改时间戳。

#include <utime.h>
int utime(const char *pathname, const struct utimbuf *buf);

参数pathname用来标识欲修改时间的文件。若该参数为符号链接,则会进一步解除引用。参数buf既可以为NULL,也可为指向utimbuf结构的指针。

 

文件描述符和打开文件的关系:多个文件描述符指向同一打开文件

内核维护的数据结构:进程级的文件描述符表,系统级的打开文件表,文件系统的i-node表。

针对每个进程,内核为其维护打开的文件的描述符表(open file descriptor),该表的每一条目都记录了单个文件描述符的相关信息:控制文件描述符操作的一组标志(目前此类标志仅定义一个,即close-on-exec标志),对打开文件句柄的引用。

 

内核对所有打开的文件维护有一个系统级的描述符表(open file description table),也称打开文件表(open file table),并将表中各条目称为打开文件句柄(open file handle)。打开文件句柄存储了与一个打开文件相关的全部信息:当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改),打开文件时所使用的状态标志(即open()的flags参数),文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式),与信号驱动I/O相关的设置,对该文件i-node 对象的引用。

 

每个文件系统都会为驻留其上的所有文件建立一个i-node表。i-node信息:文件类型(例如,常见文件、套接字或FIFO)和访问权限,一个指针(指向该文件所持有的锁的列表),文件的各种属性(包括文件大小以及与不同类型操作相关的时间戳)。

 

 

进程A中,文件描述符1和20都指向同一个打开的文件句柄(标号为23),可能是通过dup(),dup2()或fcntl()形成。进程A的文件描述符2和进程B的文件描述符2指向同一打开的文件句柄(标号为73),可能是调用fork(),或通过UNIX域套接字将一个打开的文件描述符传递给另一个进程。进程A描述符0和进程B的描述符3分别指向不同的打开文件句柄,这些句柄均指向i-node表中相同条目(1976),即指向同一文件,可能是每个进程各自对同一文件发起open()调用,也可能同一个进程两次打开同一文件。

 

要点:1.两个不同的文件描述符,若指向同一打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用read(),write()或lseek()所致),那么从另一文件描述符中也会观察到这一变化。无论这两个文件描述符分属于不同进程,还是同属于一个进程,情况都是如此。2.要获取和修改打开的文件标志(例如,O_APPEND、O_NONBLOCK和O_ASYNC),可执行fcntl()的F_GETFL和F_SETFL操作,其对作用域的约束与上一条颇为类似。3.向形之下,文件描述符标志(亦即,close-on-exec标志)为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符。

 

 

 

复制文件描述符

 

dup()调用复制一个打开的文件描述符,并返回一个新描述符,二者都指向同一打开的文件句柄。系统会保证新描述符一定是编号值最低的未用文件描述符。

#include <unistd.h>
int dup(int oldfd);

假设发起如下调用:newfd = dup(1); //标准输出(文件描述符1)

 

再假定在正常情况下,shell已经代表程序打开了文件描述符0,1,2,且没有其他描述符在用,dup()调用会创建文件描述符1的副本,返回的文件描述符编号为3。

 

如果希望返回文件描述符2,可以使用如下代码:

 

close(2);    //Frees file descriptor 2

 

newfd = dup(1);   //Should reuse file descriptor 2

 

只有当描述符0已经打开时,这段代码方可工作。如果想进一步简化上述代码,同时总是能获得所期望的文件描述符,可调用dup2()。

 

 

 

dup2()系统调用会为oldfd参数所指定的文件描述符创建副本,其编号由newfd参数指定。如果由newfd()参数所指定编号的文件描述符之前已经打开,那么dup2()会首先将其关闭。(dup2()调用会默认忽略newfd关闭期间出现的任何错误。故此,编码时更为安全的做法是:在调用dup2()之前,若newfd已经打开,则应显示调用close()将其关闭。)

#include <unistd.h>
int dup2(int oldfd, int newfd);

前述调用close()和dup()的代码可以简化为:dup2(1, 2); 若调用dup2()成功,则将返回副本的文件描述符编号(即newfd参数指定的值)。如果oldfd并非有效的文件描述符,那么dup2()调用将失败并返回错误EBADF,且不关闭newfd。如果oldfd有效,且与newfd值相等,那么dup2()将什么也不做,不关闭newfd,并将其作为调用结果返回。

 

 

文件描述符的正副本之间共享同一打开文件句柄所含的文件偏移量和状态标志。然而,新文件描述符有其自己的一套文件描述符标志,且其close-on-exec标志(FD_CLOEXEC)总是处于关闭状态。

 

 

dup3()系统调用完成的工作与dup2()相同,只是新增了一个附加参数flag,这是一个可以修改系统调用行为的位掩码。

#define _GNU_SOURCE
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);

目前,dup3()只支持一个标志O_CLOEXEC,这将促使内核为新文件描述符设置close-on-exec标志(FD_CLOEXEC)。dup3()系统调用始见于Linux 2.6.27,为Linux所独有。

 

 

 

 

/dev/fd 目录(SUSv3对/dev/fd特性未做规定,有些UNIX实现提供这一特性)

 

对于每个进程,内核都提供一个特殊的虚拟目录/dev/fd。该目录中包含/dev/fd/n形式的文件名(其中n是与进程中的打开文件描述符相对应的编号,例如/dev/fd/0就是对应进程标准输入)。打开/dev/fd目录中的一个文件等同于复制相应的文件描述符。

 

例如 fd =open("/dev/fd/1", O_WRONLY); 等同于 fd = dup(1);

/dev/fd实际上是一个符号链接欸,链接到Linux所专有的/proc/self/fd目录(Linux特有的/proc/PID/fd目录族的特例之一,此目录族中每一个目录都包含符号链接,与进程所打开的所有文件相对应)。

 

 

 

文件空洞

如果程序的文件偏移量已然跨越了文件结尾,然后再执行I/O操作,read()调用将返回0,表示文件结尾,write()调用可以在文件结尾后的任意位置写入数据。从文件结尾后到新写入数据间的这段空间被称为文件空洞。从编程角度看,文件空洞是存在字节的,读取空洞将返回以0(空字节)填充的缓冲区。然而,文件空洞不占用任何磁盘空间。直到后续,在文件空洞中写入数据,文件系统才会为之分配磁盘块。(文件空洞的优势在于稀疏填充的文件会占用较少的磁盘空间)在大多数文件系统中,文件空间的分配是以块为单位,如果空洞的边界落在块内,而非落在块边界上,则会分配一个完整的块来存储数据,块中的空洞相关部分则以空字节填充。

 

原子操作

以独占方式创建一个文件:当同时指定O_EXCL与O_CREAT作为open()的标志位时,如果要打开的文件已然存在,则open(0将返回一个错误。保证进程是打开文件的创建者,对文件是否存在的检查和创建文件属于同一原子操作。

向文件尾部追加数据:在打开文件时加入O_APPEND标志,可以保证文件偏移量的移动lseek()和数据写操作write()为原子操作。

 

 

 

 

在文件特定偏移量处的I/O:pread()和pwrite()

系统调用pread()和pwrite()完成与read()和write()相似的工作,只是前两者会在offset参数所指定的位置进行文件I/O操作,而非始于文件的当前偏移量处,且它们不会改变文件的当前偏移量。对pread()和pwrite()而言,fd所指代的文件必须是可定位的(即允许对文件描述符执行lseek()调用)。

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

pread()调用等同于将如下调用纳入同一原子操作:

off_t orig;

orig = lseek(fd, 0, SEEK_CUR);  // Save current offset

lseek(fd, offset, SEEK_SET);

s = read(fd, buf, len);

lseek(fd, orig, SEEK_SET); // Restore original file offset

进程下辖的所有线程将共享同一文件描述符表。意味着每个已打开文件的文件偏移量为所有线程所共享。当调用pread()或pwrite()时,多个线程可以同时对同一文件描述符执行I/O操作,且不会因其他线程修改文件偏移而受影响。如果使用read()或write(),那么将引发竞争状态。当多个进程的文件描述符指向相同的打开文件句柄时,使用pread()和pwrite()系统调用同样能够避免进程间出现竞争状态。

 

 

 

分散输入和集中输出(Scatter-Gather I/O)

readv()和writev()系统调用分别实现了分散输入和集中输出的功能。

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

这些系统调用并非只对单个缓冲区进行读写操作,而是一次即可传输多个缓冲区的数据。数组iov定义了一组用来传输数据的缓冲区。整型数iovcnt则指定了iov的成员个数。iov中的每个成员都是如下形式的数据结构。

struct iovec {
  void *iov_base; /* Start address of buffer */
  size_t iov_len; /* Number of bytes to transfer to/from buffer */
};

分散输入:readv()系统调用实现了分散输入功能,从文件描述符fd所指代的文件中读取一片连续的字节,然后将其散置于iov指定的缓冲区中。这一散置动作从iov[0]开始,依次填满每个缓冲区。原子性是readv()的重要属性。从调用进程的角度来看,当调用readv()时,内核在fd所指代的文件与用户内存之间一次性地完成数据转移。即使有另一进程(或线程)与其共享同一文件偏移量,且在调用readv()的同时企图修改文件偏移量,readv()所读取的数据仍将是连续的。调用readv()成功将返回读取的字节数,若文件结束EOF将返回0.调用者必须对返回值进行检查,以验证读取的字节数是否满足要求。若数据不足以填充所有缓冲区,则只会占用部分缓冲区,其中最后一个缓冲区可能只存有部分数据。

 

集中输出:writev()系统调用实现了集中输出,将iov所指定的所有缓冲区中的数据拼接起来,然后以连续的字节序列写入文件描述符fd指代的文件中。对缓冲区中数据的集中始于iov[0]所指定的缓冲区,并按数组顺序展开。writev()调用也属于原子操作,即所有数据将一次性地从用户内存传输到fd指代的文件中。因此,在向普通文件写入数据时,writev()调用会把所有的请求数据连续写入文件,而不会在其他进程(或线程)写操作的影响下(即不受其他进(线)程改变文件偏移量的影响)分散地写入文件(readv()和writev()会改变打开文件句柄的当前文件偏移量)。如同write()调用,writev()调用也存在部分写问题。

 

在指定的文件偏移量处执行分散输入/集中输出

 

Linux 2.6.30版本新增了两个系统调用:preadv()、pwritev(),将分散输出/集中输出和于指定文件偏移量处的I/O二者集于一身。它们并非标准的系统调用,但获得现代BSD的支持。

#define _BSD_SOURCE
#include <sys/uio.h>
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

preadv()和pwritev()系统调用所执行的任务与readv()和writev()相同,但执行I/O的位置将由offset参数指定(类似于preadv()和pwritev()系统调用)。

 

 

 

 

截断文件:truncate()和ftruncate()系统调用

#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);

将文件大小设置为length参数指定的值。若文件当前长度大于参数length,调用将丢弃超出部分,若小于参数length,调用将在文件尾部添加一系例空字节或是一个文件空洞。

 

 

 

 

创建临时文件

 

基于调用者提供的模板,mkstemp()函数生成一个唯一文件名并打开该文件,返回一个可用于I/O调用的文件描述符。

#include <stdlib.h>
int mkstemp(char *template)

 

模板参数采用路径名形式,其中最后6个字符必须为XXXXXX。这6个字符将被替换,以保证文件名的唯一性,且修改后的字符串将通过template参数返回(因为会对传入的template参数进行修改,所以必须将其指定为字符数组)。文件拥有者对mkstemp()函数建立的文件拥有读写权限(其他用户没有任何操作权限)且打开文件时使用了O_EXCL标志,以保证调用者以独占方式访问文件。使用unlink系统调用将该文件名删除(18.3节)。

 

tmpfile()函数会创建一个名称唯一的临时文件,并以读写方式将其打开。(打开该文件时使用了O_EXCL标志,以防一个可能性极小的冲突,即另一个进程已经创建了一个同名文件。)

#include <stdio.h>
FILE *tmpfile(void);

tmpfile()函数执行成功,将返回一个文件流供stdio库函数使用。文件流关闭后将自动删除临时文件。为达到这一目的,tmpfile()函数会在打开文件后,从内部立即调用unlink()删除该文件名。

 

文件控制操作:fcntl()

#include <fcntl.h>
int fcntl(int fd, int cmd, ...);

打开文件的状态标志

fcntl()的用途之一是针对一个打开的文件,获取或修改其访问模式和状态标志(这些值是通过指定open()调用的flag参数来设置)。要获取这些设置,应将fcntl()的cmd参数设置为F_GETFL。

int flags, accessMode;
flags = fcntl(fd, F_GETFL);  /*Third argument is not required*/
if(flags == -1)
  errExit("fcntl");
if(flags & O_SYNC)
  printf("writes are synchronized\n");

flags & O_SYNC测试文件是否以同步写方式打开

SUSv3规定:针对一个打开的文件,只有通过open()或后续fcntl()的F_SETFL操作,才能对该文件的状态进行设置。但,Linux实现与标准有所偏离:如果一个程序编译时采用了打开大文件技术,那么使用F_SETFL命令获取文件状态标志时,标志中将总是包含O_LARGEFILE标志。

判定文件的访问模式有一点复杂,这是因为O_RDONLY(0),O_WRONLY(1),O_RDWR(2)这3个常量并不与打开文件状态标志中的单个比特位对应。要判定访问模式,需要使用掩码O_ACCMODE与flags相与,将结果与3个常量进行比对。

accessMode = flags & O_ACCMODE;
if (accessMode == O_WRONLY| accessMode == O_RDWR)
  printf("file is writable\n");

可以使用F_SETFL命令修改打开文件的某些状态标志(O_APPEND,O_NONBLOCK,O_NOATIME,O_ASYNC,O_DIRECT)。系统将忽略对其他标志的修改操作。(有些其他的UNIX实现允许fcntl()修改其他标志,如O_SYNC)

 

fcntl()的F_DUPFD操作是复制文件描述符的接口:

 

newfd = fcntl(oldfd, F_DUPFD, startfd);

该调用为oldfd创建一个副本,且将使用大于等于startfd的最小未用值作为描述符编号。该调用还能保证新描述符(newfd)编号落在特定的区间范围内。Linux从2.6.24开始支持fcntl()用于复制文件描述符的附加命令:F_DUPFD_CLOEXEC。该标志不仅实现了与F_DUPFD相同的功能,还为新文件描述符设置close-on-exec标志。同样,此命令之所以得以实现,其原因也类似于open()调用中的O_CLOEXEC标志。

 

 

 

通用I/O模型外的操作:ioctl()

#include <sys/ioctl.h>
int ioctl(int fd, int request, ... /* argp */)

fd参数为某个设备或文件已打开的文件描述符,request参数指定了将在fd执行的控制操作。ioctl()根据request的参数值来确定argp所期望的类型。通常情况下,argp是指向整数或结构的指针。

 

 

混合使用库函数和系统调用进行文件I/O:

#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);

 

控制文件I/O的内核缓冲

 

 

UNIX环境高级编程 【美】W.Richard Stevens著 第3版 基于Linux 3.2.0

Web站点 www.apuebook.com

在Linux 2.4 和 Linux 2.6之间,线程的实现变为Native POSIX Thread Library(NPTL)。

文件I/O

不带缓冲指的是每个read和write都调用内核中的一个系统调用。

 

 

2017-04-14 15:52:02 wangqingchuan92 阅读数 4059

问题描述 
在存储空间有限的情况下,经常要对实时判断一个存储文件夹大小,现在用linux C编写一个线程函数,该线程能够实现循环查询某个存储文件夹的大小并通过终端打印出来,能够起到实时监控的作用。

解决方法

代码如下:
代码1:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>  
#include<stdlib.h>

void* limit_the_file_down_size(void* parameter);

int main()
{
 pthread_t pid_Limit_the_file_down_size;
 pthread_attr_t     attr ;
 pthread_attr_init(&attr);       //故障线程属性初始化
pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);                    //定义为与系统中所有线程一起竞争CPU时间
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);        //属性设置为分离状态
pthread_create(&pid_Limit_the_file_down_size,&attr,limit_the_file_down_size,NULL);
pthread_attr_destroy(&attr);                     //销毁线程

    while(1)
    {
        sleep(5);
    }

    return EXIT_SUCCESS;
}

void* limit_the_file_down_size(void* parameter)
{   
int status;
    while(1)
    {
      if((status=system("du -sh /home/fip/test/"))<0)
       {
         printf("system error.\n");
         exit(0);
       }

      sleep(1);
    }
return NULL ;
}

编译并运行
编译并运行,观察终端打印显示文件夹大小

在监控时在目标文件夹加入文件,大小改变
在监控过程中加入文件至文件夹,终端输出打印的文件夹大小数值改变
代码2:



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>  
#include<stdlib.h>

#define file_down_path "/home/fip/test/"

void* limit_the_file_down_size(void* parameter);

int main()
{
    pthread_t pid_Limit_the_file_down_size;
    pthread_attr_t  attr ;
    pthread_attr_init (&attr);              //故障线程属性初始化
    pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);          //定义为与系统中所有线程一起竞争CPU时间
    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); //属性设置为分离状态
    pthread_create(&pid_Limit_the_file_down_size,&attr,limit_the_file_down_size,NULL);
    pthread_attr_destroy(&attr);                                  //销毁线程

    while(1)
    {
        sleep(5);
    }

    return EXIT_SUCCESS;
}

void* limit_the_file_down_size(void* parameter)
{

    while(1)
    { 
        struct stat statbuff;
        float filesize;
        int sum(const char *fpath, const struct stat *sb, int typeflag)
        { 

        filesize+= sb->st_size; 
        return 0; 

        }    

        ftw(file_down_path, &sum, 1);
        filesize-=4096;////减去文件夹自身大小
        printf("!!!!test!!!! the_size_is %.3f KB\n",filesize/1000);
        filesize=0;//将filesize置0,否则会累加
        sleep(1);
    }   
    return NULL ;

编译并运行
编译并运行,观察终端打印显示文件夹大小

在监控时在目标文件夹加入文件,大小改变
在监控过程中加入文件至文件夹,终端输出打印的文件夹大小数值改变

2015-06-04 10:56:55 tennysonsky 阅读数 203861

系统调用

01、什么是系统调用?

02、Linux系统调用之I/O操作(文件操作)

03、文件描述符的复制:dup(), dup2()


多进程实现多任务

04、进程的介绍

05、Linux可执行文件结构与进程结构

06、多进程实现多任务(一):fork()

07、多进程实现多任务(二):vfork()

08、进程的控制:结束进程、等待进程结束

09、Linux特殊进程之僵尸进程

10、Linux特殊进程之孤儿进程

11、Linux特殊进程之守护进程

12、进程替换:exec函数族


进程间通信

13、为什么需要进程间通信?

14、进程间通信:信号中断处理

15、进程间通信:无名管道

16、进程间通信:有名管道

17、进程间通信:消息队列

18、进程间通信:共享内存


多线程实现多任务

19、进程和线程的区别与联系

20、线程的基本操作

21、线程堆栈大小的使用介绍

22、线程私有数据

23、线程池


多任务的同步与互斥

24、什么是同步,什么是互斥?

25、线程同步与互斥:互斥锁

26、线程同步与互斥:读写锁

27、线程同步与互斥:POSIX无名信号量

28、进程同步与互斥:POSIX有名信号量

29、进程同步与互斥:System V 信号量


拓展进阶篇

30、Linux 进程管理

31、Linux 进程调度浅析

32、浅谈进程地址空间与虚拟存储空间

33、Linux 线程浅析

34、Linux 信号列表

35、浅谈可重入函数与不可重入函数

36、浅谈标准 I/O 缓冲区

37、I/O多路复用select、poll、epoll的区别使用


2014-05-14 20:13:15 davidsky11 阅读数 1872

         利用系统调用stat()、lstat()以及fstat(),可获取与文件有关的信息,其中大部分提取自文件i节点。以上3个系统调用之间仅有的区别在于对文件的描述方式不同。

         1  stat()返回所命名文件的相关信息。

         2  lstat()与stat()类似,区别在于如果文件属于符号链接,那么所返回的信息针对的是符号链接自身(而非符号链接所指向的文件)。

         3  fstat()则返回由某个打开文件描述符所指代文件的相关信息。

         系统调用stat()和lstat()不需要对所操作的文件本身拥有任何权限,但针对指定pathname的父目录要有执行(搜索)权限。而只要为它提供有效的文件描述符,fstat()系统调用总是成功的。

         上述所有系统调用都会在缓冲区中返回一个由statbuf指向的stat结构,其格式如下:

         

设备ID和i节点号

         st_dev字段标识文件所驻留的设备。st_ino字段包含了文件的i节点号。利用这两个字段,可以在所有文件系统中唯一表示某个文件。

         如果是针对设备的i节点,那么st_rdev字段则包含设备的主、辅ID。利用宏major()和minor(),可以提取主、辅ID。在Linux系统上,要使用这两个宏,需要定义_BSD_SOURCE宏,然后include定义这两个宏的头文件<sys/types.h>。

         

         由于major()和minor()所返回的整型值大小随UINX实现的不同而各有不用。为保证可移植性,打印时总是将返回值强制转换为long。

 

文件所有权

         st_uid和st_gid字段分别表示文件的属主(用户ID)和属组(组ID)。

 

链接数

         st_nlink字段包含了指向文件的(硬)链接数。

 

文件类型及权限

         st_mode字段内含有位掩码,起表示文件类型和指定文件权限的双重作用。下图为该字段所含各位的布局情况。 

文件类型

权限

 

 

 

 

U

G

T

R

W

X

R

W

X

R

W

X

         Linux使用了st_mode字段中的4位来标识文件类型位。st_mode字段与常量S_IFMT相与(&),可从该字段中提取出文件类型。将计算结果与一系列常量进行比较,即可确定文件类型:

         if((statbuf.st_mode & S_IFMT) == S_IFREG)

                   printf(“regularfile\n”);

         在Linux中可利用标准宏将其简化:

         if(S_ISREG(statbuf.st_mode))

                   printf(“regularfile\n”);

 

常  量

测  试  宏

文  件  类  型

S_IFREG

S_ISREG()

常规文件

S_IFDIR

S_ISDIR()

目录

S_IFCHR

S_ISCHR()

字符设备

S_IFBLK

S_ISBLK()

块设备

S_IFIFO

S_ISFIFO()

FIFO或管道

S_IFSOCK

S_ISSOCK()

套接字

S_IFLNK

S_ISLNK()

符号链接

针对stat结构中的st_mode来检查文件类型的宏

         想从<sys/stat.h>中获取S_IFSOCK和S_ISSOCK()的定义,必须定义_BSD_SOURCE特性测试宏,或将_XOPEN_SOURCE定义为不小于500的值。

         st_mode字段的低12位定义了文件权限。最低9位分别用来表示文件属主、属组以及其他用户的读、写、执行权限。

 

文件大小、已分配块以及最优I/O块大小

         对于常规文件,st_size字段表示文件的字节数。对于符号链接,st_size字段则表示链接所指路径名的长度,以字节为单位。对于共享内存对象,该字段则表示对象的大小。

         st_blocks字段表示分配给文件的总块数,块大小为512字节,其中包括了为指针块所分配的空间。st_blocks字段记录了实际分配给文件的磁盘块数量。如果文件内含空洞,该值将小于从相应文件字节数字段(st_size)的值。

         st_blksize字段所指并非底层文件系统的块大小,而是针对文件系统上文件进行I/O操作时的最优块大小(以字节为单位)。一般而言,st_blksize的返回值为4096。

 

文件时间戳

         st_time、st_mtime和st_ctime字段,分别记录了对文件的上次访问时间、上次修改时间以及文件状态发送改变的上次时间。这3个字段的类型都是time_t,记录了自新纪元(Epoch)以来的秒数。

 

示例程序

(获取并解释文件的stat信息)

#define _BSD_SOURCE
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#define FP_SPECIAL 1
#define STR_SIZE sizeof("rwxrwxrwx")

typedef enum { FALSE, TRUE } Boolean;

static char *filePermStr(mode_t perm, int flags)
{
	static char str[STR_SIZE];
	snprintf(str, STR_SIZE, "%c%c%c%c%c%c%c%c%c",
		(perm & S_IRUSR) ? 'r' : '-', (perm & S_IWUSR) ? 'w' : '-',
		(perm & S_IXUSR) ? 
			(((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
			(((perm & S_ISUID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
		(perm & S_IRGRP) ? 'r' : '-', (perm & S_IWGRP) ? 'w' : '-',
		(perm & S_IXGRP) ? 
			(((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 's' : 'x') :
			(((perm & S_ISGID) && (flags & FP_SPECIAL)) ? 'S' : '-'),
		(perm & S_IROTH) ? 'r' : '-', (perm & S_IWOTH) ? 'w' : '-',
		(perm & S_IXOTH) ? 
			(((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 't' : 'x') :
			(((perm & S_ISVTX) && (flags & FP_SPECIAL)) ? 'T' : '-'));
			
	return str;
}

static void showStatInfo(struct stat *st)
{
	printf("File type:			");
	
	switch(st->st_mode & S_IFMT)
	{
		case S_IFREG:	printf("regular file\n");		break;
		case S_IFDIR:	printf("directory\n");			break;
		case S_IFCHR:	printf("character device\n");	break;
		case S_IFBLK:	printf("block device\n");		break;
		case S_IFLNK:	printf("symbolic (soft) link\n");	break;
		case S_IFIFO:	printf("FIFO or pipe\n");		break;
		case S_IFSOCK:	printf("socket\n");				break;
		default:		printf("unknown file type?\n");	break;
	}
	
	printf("Device containing i-node: 	major=%ld	minor=%ld\n", 
		(long) major(st->st_dev), (long) minor(st->st_dev));
		
	printf("I-node number:			%ld\n", (long) st->st_ino);
	printf("Mode:				%lo (%s)\n", (unsigned long) st->st_mode, 
		filePermStr(st->st_mode, 0));
		
	if (st->st_mode & (S_ISUID| S_ISGID| S_ISVTX))
		printf("	special bits set:	%s%s%s\n",
			(st->st_mode & S_ISUID) ? "set-UID " : "",
			(st->st_mode & S_ISGID) ? "set-GID " : "",
			(st->st_mode & S_ISVTX) ? "sticky " : "");
			
	printf("Number of (hard) links:		%ld\n", (long) st->st_nlink);
	printf("Ownership:			UID=%ld	GID=%ld\n",
		(long) st->st_uid, (long) st->st_gid);
		
	if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
		printf("Device number (st_rdev):		major=%ld; minor=%ld\n",
			(long) major(st->st_rdev), (long) minor(st->st_rdev));
			
	printf("File size:			%lld bytes\n", (long long) st->st_size);
	printf("Optimal I/O block size:		%ld bytes\n", (long) st->st_blksize);
	printf("512B blocks allocated:		%lld\n", (long long) st->st_blocks);
	printf("Last file access:		%s\n", ctime(&st->st_atime));
	printf("Last file modification:		%s\n", ctime(&st->st_mtime));
	printf("Last status change:		%s\n", ctime(&st->st_ctime));
}

int main(int argc, char *argv[])
{
	struct stat st;
	Boolean statLink;
	int fname;
	
	statLink = (argc > 1) && strcmp(argv[1], "-l") == 0;
	
	fname = statLink ? 2: 1;
	
	if (fname >= argc || (argc > 1 && strcmp(argv[1], "--help") == 0))
		printf("%s [-l] file\n 		-l = use lstat() instead of stat()\n", argv[0]);
			
	if (statLink)
	{
		if (lstat(argv[fname], &st) == -1)
		{
			printf("lstat() operate failed!\n");
		}
	}
	else
	{
		if (stat(argv[fname], &st) == -1)
			printf("stat() operate failed!\n");
	}	
	showStatInfo(&st);
	exit(1);
}

以下是运行结果: