APUE-文件I/O
库函数和系统调用
库函数调用 | 系统调用 |
---|---|
在所有的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 World\n"
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: %s\n",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: %s\n",strerror(errno));
goto cleanup;
}
printf("Read %d bytes data from file: %s\n",rv,buf);
cleanup:
close(fd);
/*非main函数return只会导致函数退出
*main函数的return会调用exit(),导致整个进程中止
*同理,其他非main函数调用exit()一样会导致整个进程中止
*程序结束后,可以通过"echo $?"命令查看返回值*/
return 0;
}
strlen()和sizeof()的区别:
sizeof()
是一个运算符,而strlen()
是一个函数。sizeof()
计算的是变量或类型所占用的内存字节数,而strlen()
计算的是字符串中字符的个数。sizeof()
可以用于任何类型的数据,而strlen()
只能用于以空字符 '\0' 结尾的字符串。sizeof()
计算字符串的长度,包含末尾的 '\0',strlen()
计算字符串的长度,不包含字符串末尾的 '\0'。
程序编译运行
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: %s\n",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=%d\n",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: %luB\n",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 OK\n");
}
//测试写许可权
if(access(TEST_FILE,W_OK)==0)
{
printf("WRITE OK\n");
}
//测试执行许可权
if(access(TEST_FILE,X_OK)==0)
{
printf("EXEC OK\n");
}
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: %s\n",TEST_DIR,strerror(errno));
return -1;
}
/* 更改当前工作路径到文件夹dir下 */
if( chdir(TEST_DIR) < 0 )
{
printf("change directory '%s' failure: %s\n",TEST_DIR,strerror(errno));
rv = -2;
goto cleanup;
}
/* 在dir文件夹下 创建普通文本文件file1.txt,并设置其权限位为644 */
if( (fd1 = creat("file1.txt",0644)) < 0 )
{
printf("creat file1.txt failure: %s\n",strerror(errno));
rv = -3;
goto cleanup;
}
/* 在dir文件夹下 创建普通文本文件file2.txt,并设置其权限位为644 */
if( (fd1 = creat("file2.txt",0644)) < 0 )
{
printf("creat file2.txt failure: %s\n",strerror(errno));
rv = -4;
goto cleanup;
}
/* 更改当前工作路径到父目录去 */
if( chdir("../") < 0 )
{
printf("change directory to '%s' failure: %s\n",TEST_DIR,strerror(errno));
rv = -5;
goto cleanup;
}
/* 打开dir文件夹 */
if ((dirp = opendir(TEST_DIR)) == NULL)
{
printf("opendir %s error: %s\n", TEST_DIR, strerror(errno));
rv = -6;
goto cleanup;
}
/* 列出dir里面的所有文件和文件夹,一个文件夹下至少要有.和..两个文件,从文件夹下读时,每个文件都是一个dirent*/
while((direntp = readdir(dirp)) != NULL)
{
printf("find file: %s\n",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
热门相关:锦衣当国 扑倒老公大人:龙总,我爱你! 暖君 史上第一宠婚:慕少的娇妻 朔明