APUE-文件I/O

  • APUE-文件I/O已关闭评论
  • 66 次浏览
  • A+
所属分类:linux技术
摘要

​ 在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。


库函数和系统调用

库函数调用 系统调用
在所有的ANSI C编译器中,C库函数都是相同的 各个操作系统的系统调用是不同的,这导致程序不可移植
它调用库函数中的一段程序(或函数) 它调用系统内核的服务
与用户程序相联系 在内核地址空间执行
它的运行时间属于“用户时间” 运行时间属于“系统时间”
属于过程调用,调用开销较小 需要在用户控件和内核 上下文环境切换,开销较大
在C函数库libc中大约有300个函数 在UNIX中大约有90个系统调用
典型的C函数库:printf、fopen、fread、malloc 典型的系统调用:write、open、read、sbrk、fork

文件I/O系统调用

文件和文件描述符

文件扩展名

​ 在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。

  • .tar,.tar.gz,.tgz,zip,.tar.bz表示压缩文件,创建命令为tar,gzip,unzip等
  • .sh文件表示shell脚本文件
  • .pl 表示perl语言文件
  • .py 表示python语言文件
  • .conf 表示系统服务的配置文件
  • .c表示C文件
  • .h头文件
  • .cpp表示C++源文件
  • .so 表示动态库文件
  • ·a 表示静态库文件

文件类型

​ Linux系统中把一切都看做文件,Linux有7中类型文件:普通文件-、目录(dierectory)文件、符号(link)链接、字符(character)设备文件、块(block)设备文件、管道(pipe)文件、套接字(socket)文件。其中文件、目录、符号链接会占用磁盘空间来存储,而块设备、字符设备、套接字、管道是伪文件,并不占用磁盘空间。

​ 通过ls -l 命令查看文件类型

文件类型标识 文件类型
- 普通文件
d 目录文件
l 符号链接
c 字符设备
b 块设备
p 管道
s 套接字socket

文件描述符

​ 文件描述符(file descriptor, fd) 是Linux内核为了高效管理已被打开的文件所创建的索引, 其是一个非负整数(通常是小整数) , 用于指代被打开的文件, 所有执行I/O操作的系统调用都通过文件描述符。程序在开始运行时,系统会自动打开三个文件描述符, 0是标准输入, 1是标准输出, 2是标准错误。POSIX标准要求每次打开文件时(含socket) 必须使用当前进程中最小可用的文件描述符号码, 因此第一次打开的文件描述符一定是3.

文件描述符 用途 POSIX文件描述符 标准I/O文件流
0 标准输入 STDIN_FILENO stdin
1 标准输出 STDOUT_FILENO stdout
2 标准出错 STDERR_FILENO stderr

文件I/O操作

程序编写

/*头文件包含,可以通过man手册查找*/ #include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  /*MSG_STR是写到文件中的内容,是一个常量,会保存到程序的只读数据段中*/ #define BUFSIZE 1024 #define MSG_STR "Hello Worldn"  int main(int argc,char *argv[]) {         int fd = -1;	//文件描述符,正常应该为非负的整数         int rv = -1;	//返回值return value         char buf[BUFSIZE];	//存放从文件中读到的数据          fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666);	//调用open()系统调用并返回一个文件描述符保存在fd中         if(fd < 0)         {             /*perror()函数可以打印出错的原因,但是不能处理要进行格式化控制的输出或写入日志文件 */                       perror("Open/Creat file test.txt failure");                       return 0;         }         printf("Open file returned file descriptor [%d]n",fd);         /* write(写到哪里,写什么,写的大小)     	 *用strlen是因为MSG_STR是宏定义,宏定义是地址*/         if((rv = write(fd,MSG_STR,strlen(MSG_STR))) < 0)         {             /*strerror()函数可以将整型类型的出错原因errno转换成相应的字符串形式*/                 printf("Write %d bytes into file failure: %sn",rv,strerror(errno));                 goto cleanup;	//在做错误处理时一般会用goto,做集中的错误处理         }          /*test.txt里有成功写入,但总是Read 0 bytes data from file,这是因为在Hello World写入后,文件偏移量(类似光标)位于World的后方,读或写入时从光标所在位置开始读写,所以读不到值,通过lseek()系统调用将文件偏移量设置到第一个字节后,Read 12 bytes data from file: Hello World */     //lseek(fd, 0, SEEK_SET);      /*buf在栈中,栈中的数据是随机数,在使用buf时不对buf进行初始化,会导致后面打印出的是个随机数。因此在往buf中写内容前要先用memset将buf中的内容清掉*/         memset(buf,0,sizeof(buf));	         if( (rv=read(fd,buf,sizeof(buf))) < 0 )         {                 printf("Read data from file failure: %sn",strerror(errno));                 goto cleanup;         }          printf("Read %d bytes data from file: %sn",rv,buf);  cleanup:         close(fd);          /*非main函数return只会导致函数退出          *main函数的return会调用exit(),导致整个进程中止          *同理,其他非main函数调用exit()一样会导致整个进程中止          *程序结束后,可以通过"echo $?"命令查看返回值*/         return 0; } 

strlen()和sizeof()的区别:

  • sizeof() 是一个运算符,而 strlen() 是一个函数。
  • sizeof() 计算的是变量或类型所占用的内存字节数,而 strlen() 计算的是字符串中字符的个数。
  • sizeof() 可以用于任何类型的数据,而 strlen() 只能用于以空字符 '' 结尾的字符串。
  • sizeof() 计算字符串的长度,包含末尾的 '',strlen() 计算字符串的长度,不包含字符串末尾的 ''。

程序编译运行

li@ubuntu22:~/apue$ gcc file_io.c -o file_io li@ubuntu22:~/apue$ ./file_io Open file returned file descriptor [3] Read 0 bytes data from file: li@ubuntu22:~/apue$ ./file_io Open file returned file descriptor [3]	#再运行一次还是3,描述符是当前进程最小可用的,第二次运行是新的进程 Read 0 bytes data from file: li@ubuntu22:~/apue$ cat test.txt #通过cat test. txt命令查看文件内容验证 "Hello World"字符串确实被wirte()系统调用写入到了文件中 Hello World 

文件I/O操作函数

open()系统调用

int open(const char *path, int :flag, ... /*mode_t mode*/); 

open()系统调用用来打开一个文件, 并返回一个文件描述符(file description), 并且该文件描述符是当前进程,最小、未使用的文件描述符数值。

参数: path: 要打开的文件、设备的路径
flag: 由多个选项进行 “或”运算构造flag参数。

​ 必选:O_RDONLY(只读)、 O_WRONLY(只写)、 O_RDWR(读写)

​ 可选:O_APPEND 每次写时都追加到文件的尾端。

​ O_CREAT 文件不存在则创建它, 使用该选项需要第三个参数mode

​ O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0

​ O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。

​ O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY....

mode: oflag带O_CREAT选项时可以用来创建文件, 这时必须带该参数用来指定创建文件的权限模式,如0666。否则不需要。使用示例代码: 
int fd; fd = open("text. txt", O_RDWR|O_CREAT|O_TRUNC, 0666);	//O_RDWR文件打开可以通过文件描述符可读可写,O_CREAT文件不存在则创建,O_TRUNC如果文件中有内容先把文件清掉 fd =open("text. txt", O_WRONLY|O_APPEND);	//O_WRONLY只写,O_APPEND追加到文件尾,如果文件不存在则返回-1 

creat()系统调用

/*用来创建一个新文件并返回其fd,等价于open(path, O_WRONLY|O_CREAT|O_TRUNC,mode);*/ int creat(const char *path,mode_t mode); 

close()系统调用

/*用来关闭一个打开的文件描述符*/ int close(int fd); 

write()系统调用

/*用来往打开的文件描述符fd指向的文件中写入buf指向的数据,nbytes指定要写入的数据大小*/ size_t write(int fd,const void *buf,size_t nbytes); 

read()系统调用

/*用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,nbytes一般是buf剩余的空间大小*/ size_t read(int fd,void *buf,size_t nbytes); 

lseek()系统调用

off_t lseek(int fd, off_t offset, int whence); 

我们在从文件里读出内容, 或往文件写如内容的时候都有一个起始地址, 这个起始地址就是当前文件偏移量,当我们对文件进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标, 我们读或写入时从光标所在位置开始读写, 每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。

其中 whence 可以是以下三个值

whence 位置
SEEK_SET 文件头
SEEK_CUR 当前位置
SEEK_END 文件尾

而offset就是相对于whence 的偏移量, 譬如:

lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上

lseek(fd, 0, SEEK_END); 将文件偏移量设置到文件最后一个字节上

lseek(fd, -1, SEEK_END); 将文件偏移量设置到文件最后的倒数第一个字节上

dup()和dup2()系统调用

int dup(int fd); int dup2(int fd,int fd2); 

这两个函数都可以用来复制一个新的文件描述符来指向fd对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。

dup0返回的新文件描述符一定是当前可用文件描述符中的最小数值

dup2可以用fd2参数来指定新的文件描述符。如果fd2已经打开,则先关闭。如fd等于fd2,则dup2返回fd2,而不关闭它。

#include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main(int argc,char *argv[]) {         int fd = -1;          fd = open("std.txt",O_RDWR|O_CREAT|O_TRUNC,0666);         if( fd < 0 )         {                 printf("Open file failure: %sn",strerror(errno));                 return 0;         }          close(0);	//用dup()先close()         close(1);         close(2); 		dup(fd);    //0 标准输入 		dup(fd);    //1 标准输出 		dup(fd);    //2 标准错误输出  //    dup2(fd, STDIN_FILENO);     // 标准输入重定向到fd //    dup2(fd, STDOUT_FILENO);    // 标准输出重定向到fd //    dup2(fd, STDERR_FILENO);    // 标准错误输出重定向到fd          printf("fd=%dn",fd);          close(fd);         return 0; } 
li@ubuntu22:~/apue$ vim redirect_stdio.c li@ubuntu22:~/apue$ gcc redirect_stdio.c -o redirect_stdio li@ubuntu22:~/apue$ ./redirect_stdio li@ubuntu22:~/apue$ echo $? 0 li@ubuntu22:~/apue$ ls file_io  file_io.c  redirect_stdio  redirect_stdio.c  std.txt  test.txt li@ubuntu22:~/apue$ cat std.txt fd=3 

stat()和fstat()系统调用

int stat(const char *restrict path,struct stat *restrict buf); int fstat(int fd,struct stat *buf); 

用于返回文件或目录相关信息,stat()第一个参数是文件名,而fstat()第一个参数是文件打开的相应文件描述符。

struct stat结构体的定义:

struct stat {     dev_t st_dev;         // 文件的设备 ID     ino_t st_ino;         // 文件的 inode 号     mode_t st_mode;       // 文件类型和权限     nlink_t st_nlink;     // 文件的硬链接数目     uid_t st_uid;         // 文件所有者的用户 ID     gid_t st_gid;         // 文件所有者的组 ID     dev_t st_rdev;        // 设备文件的设备 ID     off_t st_size;        // 文件总大小(以字节为单位)     blksize_t st_blksize; // 文件系统的 I/O 缓冲区大小     blkcnt_t st_blocks;   // 分配给文件的磁盘块数     time_t st_atime;      // 最后一次访问时间     time_t st_mtime;      // 最后一次修改时间     time_t st_ctime;      // 最后一次更改时间(修改文件的权限或者所有者) }; 
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h>  int main(int argc,char **argv) {         struct stat stbuf;          stat("stat.c",&stbuf);              /*stbuf是一个指针,在栈区,它指向的地址是一个随机地址(野指针),使用指针一定要先初始化:         1、 使用malloc         	struct stat *stbuf;         	stbuf = malloc(sizeof(struct stat))         2、定义一个变量stbuf,传&stbuf         	struct stat stbuf;         	stat("stat.c",&stbuf);         */         //struct stat *stbuf;         //stat("stat.c",stbuf);               	/*fstat()要先用open()后使用*/ //    	int fd = -1; //    	fd = open("stat.c",O_RSONLY); //    	fstat(fd,stbuf);                   printf("File Mode: %o Real Size: %luB,Space Size: %luBn",stbuf.st_mode,stbuf.st_size,stbuf.st_blksize);          return 0; } 
li@ubuntu22:~/apue$ vim stat.c li@ubuntu22:~/apue$ gcc stat.c -o stat li@ubuntu22:~/apue$ ./stat File Mode: 100664 Real Size: 283B,Space Size: 4096B li@ubuntu22:~/apue$ 

access()系统调用

int access(const char *path,int mode) 

用来测试文件是否存在或测试其权限位,其中第一个参数path是相应的文件路径名,第二个参数是要测试的模式。

mode说明:

模式 说明
R_OK 测试读许可权
W_OK 测试写许可权
X_OK 测试执行许可权
F_OK 测试文件是否存在
#include <stdio.h> #include <unistd.h>  #define TEST_FILE "access.c"  int main(int argc,char *argv[]) {     	//测试文件是否存在         if(access(TEST_FILE,F_OK) != 0)         {                 printf("File %s not exist!n",TEST_FILE);                 return 0;         }          printf("File %s not exist!n",TEST_FILE);      	//测试读许可权         if(access(TEST_FILE,R_OK)==0)         {                 printf("READ OKn");         } 		//测试写许可权         if(access(TEST_FILE,W_OK)==0)         {                 printf("WRITE OKn");         } 		//测试执行许可权         if(access(TEST_FILE,X_OK)==0)         {                 printf("EXEC OKn");         }          return 0; } 
li@ubuntu22:~/apue$ ./access File access.c not exist! READ OK WRITE OK 

unlink()系统调用

用来删除文件,其本质是让文件的链接记数自减,当链接记数减为0时,文件会被自动删除。(Linux下rm命令其实就是调用了unlink()系统调用)

rename()系统调用

将文件重命名

文件夹操作相关系统调用

函数原型 函数说明
int mkdir(const char *pathname,mode_t mode) 创建文件夹
int rmdir(const char *pathname) 删除文件夹(文件夹必须为空)
DIR *opendir(const char *pathname) 打开文件夹
struct dirent *readdir(DIR *dp) 读文件夹
int closedir(DIR *dp) 关闭文件夹
int chdir(const char *pathname) 改变工作目录

readdir()系统调用的struct dirent:

struct dirent {   long d_ino; /* inode number 索引节点号 */     off_t d_off; /* offset to this dirent 在目录文件中的偏移 */     unsigned short d_reclen; /* length of this d_name 文件名长 */     unsigned char d_type; /* the type of d_name 文件类型 */     char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */ } 
#include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  #define TEST_DIR "dir"  int main(int argc,char *argv[]) {         int rv = 0;         int fd1 = -1;         int fd2 = -1;         DIR *dirp = NULL;         struct dirent *direntp = NULL;      	/* 创建文件夹,文件权限755 */         if( mkdir(TEST_DIR,0755) < 0 )         {                 printf("creat directory '%s' failure: %sn",TEST_DIR,strerror(errno));                 return -1;         }      	/* 更改当前工作路径到文件夹dir下 */         if( chdir(TEST_DIR) < 0 )         {                 printf("change directory '%s' failure: %sn",TEST_DIR,strerror(errno));                 rv = -2;                 goto cleanup;         }          	/* 在dir文件夹下 创建普通文本文件file1.txt,并设置其权限位为644 */ 		if( (fd1 = creat("file1.txt",0644)) < 0 )         {                 printf("creat file1.txt failure: %sn",strerror(errno));                 rv = -3;                 goto cleanup;         }      	/* 在dir文件夹下 创建普通文本文件file2.txt,并设置其权限位为644 */         if( (fd1 = creat("file2.txt",0644)) < 0 )         {                 printf("creat file2.txt failure: %sn",strerror(errno));                 rv = -4;                 goto cleanup;         }     		 /* 更改当前工作路径到父目录去 */         if( chdir("../") < 0 )         {                 printf("change directory to '%s' failure: %sn",TEST_DIR,strerror(errno));                 rv = -5;                 goto cleanup;         }      	/* 打开dir文件夹 */         if ((dirp = opendir(TEST_DIR)) == NULL)         {                 printf("opendir %s error: %sn", TEST_DIR, strerror(errno));                 rv = -6;                 goto cleanup;         }          	/* 列出dir里面的所有文件和文件夹,一个文件夹下至少要有.和..两个文件,从文件夹下读时,每个文件都是一个dirent*/ 		while((direntp = readdir(dirp)) != NULL)         {                 printf("find file: %sn",direntp->d_name);         }      	/* 关闭所有打开的文件夹 */         closedir(dirp);         return rv;  cleanup:         if(fd1 >= 0)         {                 close(fd1);         }          if(fd2 >= 0)         {                 close(fd2);         } } 
li@ubuntu22:~/apue$ vim dir.c li@ubuntu22:~/apue$ gcc dir.c -o dir li@ubuntu22:~/apue$ ./dir find file: file1.txt find file: .. find file: file2.txt find file: . li@ubuntu22:~/apue$ ls directory -la total 8 drwxr-xr-x 2 li li 4096  9月 24 21:16 . drwxrwxr-x 3 li li 4096  9月 24 21:16 .. -rw-r--r-- 1 li li    0  9月 24 21:16 file1.txt -rw-r--r-- 1 li li    0  9月 24 21:16 file2.txt