Linux编程——文件 IO操作【转】

(转自:https://blog.csdn.net/baidu_28312631/article/details/47828711

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> 函数原型:  

[cpp] view plain copy
  1. 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 

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/types.h>  
  4. #include <sys/stat.h>  
  5. #include <fcntl.h>  
  6.   
  7. #define FILE_PATH   "./test.txt"  
  8.   
  9. int main(void)  
  10. {  
  11.     int fd;  
  12.     if ((fd = open(FILE_PATH, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {  
  13.         printf("open error\n");  
  14.         exit(-1);  
  15.     } else {  
  16.         printf("open success\n");  
  17.     }  
  18.     return 0;  
  19. }  

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

    2. read

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

    函数原型:

[cpp] view plain copy
  1. 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> 函数原型:

[cpp] view plain copy
  1. ssize_t write(int fd, const void * buf, size_t count)  

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

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

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

    read和write使用范例

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4.   
  5. int main(void)  
  6. {  
  7.     char buf[100];  
  8.     int num = 0;  
  9.   
  10.     // 获取键盘输入,还记得POSIX的文件描述符吗?  
  11.     if ((num = read(STDIN_FILENO, buf, 10)) == -1) {  
  12.         printf ("read error");  
  13.         error(-1);  
  14.     } else {  
  15.     // 将键盘输入又输出到屏幕上  
  16.         write(STDOUT_FILENO, buf, num);  
  17.     }  
  18.   
  19.     return 0;  
  20. }  

    4. close

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

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

    5. lseek

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

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

[cpp] view plain copy
  1. 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可为正或负
[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/types.h>  
  4. #include <unistd.h>  
  5. #include <fcntl.h>  
  6.   
  7. int main(int argc, char * argv[])  
  8. {  
  9.     int fd;  
  10.     char buf[100];  
  11.     if ((fd = open(argv[1], O_RDONLY)) < 0) {  
  12.         perror("open");  
  13.         exit(-1);  
  14.     }  
  15.     read(fd, buf, 1);  
  16.     write(STDOUT_FILENO, buf, 1);  
  17.     lseek(fd, 2, SEEK_CUR);  
  18.   
  19.     read(fd, buf, 1);  
  20.     write(STDOUT_FILENO, buf, 1);  
  21.     lseek(fd, -1, SEEK_END);  
  22.   
  23.     read(fd, buf, 1);  
  24.     write(STDOUT_FILENO, buf, 1);  
  25.     lseek(fd, 0, SEEK_SET);  
  26.   
  27.     read(fd, buf, 1);  
  28.     write(STDOUT_FILENO, buf, 1);  
  29.     close(fd);  
  30.     printf("\n");  
  31.   
  32.     return 0;  
  33. }  

    6. select

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

[cpp] view plain copy
  1. // /dev/input/mice 是鼠标的设备文件  
  2. fd = open("/dev/input/mice", O_RDONLY);  
  3. read(0, buf, 100);  
  4. read(fd, buf, 100);  

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

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

[cpp] view plain copy
  1. 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   

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <sys/select.h>  
  3. #include <fcntl.h>  
  4. #include <unistd.h>  
  5.   
  6. #define MAXNUM      100  
  7. #define OPEN_DEV    "/dev/input/mice"  
  8.   
  9. int main(void)  
  10. {  
  11.     fd_set rfds;  
  12.     struct timeval tv;  
  13.     int retval, fd;  
  14.     char buf[MAXNUM];  
  15.   
  16.     fd = open(OPEN_DEV, O_RDONLY);  
  17.     while (1) {  
  18.         FD_ZERO(&rfds);  
  19.         FD_SET(0, &rfds);  
  20.         FD_SET(fd, &rfds);  
  21.         tv.tv_sec = 5;  
  22.         tv.tv_usec = 0;  
  23.   
  24.         retval = select(fd+1, &rfds, NULL, NULL, &tv);  
  25.         if (retval < 0)  
  26.             printf ("error\n");  
  27.         if (retval == 0)  
  28.             printf ("No data within 5 seconds\n");  
  29.         if (retval > 0) {  
  30.             if (FD_ISSET(0, &rfds)) {  
  31.                 printf ("Data is available from keyboard now\n");  
  32.                 read(0, buf, MAXNUM);  
  33.             }  
  34.             if (FD_ISSET(fd, &rfds)) {  
  35.                 printf ("Data is available from mouse now\n");  
  36.                 read(fd, buf, MAXNUM);  
  37.             }  
  38.         }  
  39.     }  
  40.     return 0;  
  41. }  

   

   stat 的使用

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

    Linux编程——文件 IO操作【转】

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

    1. stat 的基本使用

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

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

[cpp] view plain copy
  1. int stat(const char \* path, struct stat \* buf)  

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

[cpp] view plain copy
  1. struct stat {  
  2.     dev_t       st_dev;  
  3.     ino_t       st_ino;  
  4.     mode_t      st_mode;  
  5.     nlink_t     st_nlink;  
  6.     uid_t       st_uid;  
  7.     gid_t       st_gid;  
  8.     dev_t       st_rdev;  
  9.     off_t       st_size;  
  10.     blksize_t   st_blksize;  
  11.     blkcnt_t    st_blocks;  
  12.     time_t      st_atime;  
  13.     time_t      st_mtime;  
  14.     time_t      st_ctime;  
  15. };  

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

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

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/stat.h>  
  4. #include <sys/types.h>  
  5. #include <unistd.h>  
  6.   
  7. int main(int argc, char **argv)  
  8. {  
  9.     struct stat     buf;  
  10.     if(argc != 2) {   
  11.         printf("Usage: stat <pathname>");   
  12.         exit(-1);   
  13.     }  
  14.     if(stat(argv[1], &buf) != 0) {   
  15.         printf("stat error.");   
  16.         exit(-1);   
  17.     }  
  18.     printf("#i-node:    %ld\n", buf.st_ino);  
  19.     printf("#link:      %d\n", buf.st_nlink);  
  20.     printf("UID:        %d\n", buf.st_uid);  
  21.     printf("GID:        %d\n", buf.st_gid);  
  22.     printf("Size        %ld\n", buf.st_size);  
  23.     exit(0);  
  24. }  

     2. 文件类型的判定

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

    Linux编程——文件 IO操作【转】

    我们修改上面的例子:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/stat.h>  
  4. #include <sys/types.h>  
  5. #include <unistd.h>  
  6.   
  7. int main(int argc, char **argv)  
  8. {  
  9.     struct stat buf;  
  10.     char * file_mode;  
  11.     if(argc != 2) {  
  12.         printf("Usage: stat <pathname>\n");   
  13.         exit(-1);   
  14.     }  
  15.     if(stat(argv[1], &buf) != 0) {  
  16.         printf("stat error.\n");   
  17.         exit(-1);   
  18.     }  
  19.     if (S_ISREG(buf.st_mode))  
  20.         file_mode = "-";  
  21.     else if (S_ISDIR(buf.st_mode))  
  22.         file_mode = "d";  
  23.     else if (S_ISCHR(buf.st_mode))  
  24.         file_mode = "c";  
  25.     else if(S_ISBLK(buf.st_mode))  
  26.         file_mode = "b";  
  27.     printf("#i-node:    %ld\n", buf.st_ino);  
  28.     printf("#link:      %d\n", buf.st_nlink);  
  29.     printf("UID:        %d\n", buf.st_uid);  
  30.     printf("GID:        %d\n", buf.st_gid);  
  31.     printf("Size        %ld\n", buf.st_size);  
  32.     printf("mode: %s\n", file_mode);  
  33.     exit(0);  
  34. }  

     

    目录操作

    当目标是目录而不是文件的时候,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

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

[cpp] view plain copy
  1. struct dirent {  
  2.    ino_t   d_ino;  
  3.    off_t   d_off;  
  4.    unsigned short  d_reclen;  
  5.    unsigned char   d_type;  
  6.    char    d_name[256];  
  7. };  

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

    3. 关闭目录

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

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

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

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

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <dirent.h>  
  4. int main(int argc, char *argv[])  
  5. {  
  6.     DIR *dp;  
  7.     struct dirent *entp;  
  8.     if (argc != 2) {  
  9.         printf("usage: showdir dirname\n");  
  10.         exit(0);  
  11.     }  
  12.     if ((dp = opendir(argv[1])) == NULL) {  
  13.         perror("opendir");  
  14.         exit(-1);  
  15.     }  
  16.     while ((entp = readdir(dp)) != NULL)  
  17.         printf("%s\n", entp->d_name);  
  18.   
  19.     closedir(dp);  
  20.     return 0;  
  21. }