文件IO
标准C库IO函数
- 标准C库IO函数是可跨平台的,效率会更高。
- Linux系统的IO API 相比 标准C库IO函数 更低层,
Linux:文件描述符(Windows:文件句柄)
I/O缓冲区:当向文件中写入数据时,会先将数据放入I/O缓冲区,等待缓冲区满的时候,再调用Linux的IO API将缓冲区的数据写入文件中,从而提高效率(因为写入文件 往硬盘中写入数据本身效率很低,所以尽量少地调用,降低写磁盘的次数)。
Linux的IO API没有I/O缓冲区。
标准C库IO vs. Linux系统IO
虚拟地址空间
MMU(逻辑管理单元,位于CPU):将虚拟地址空间映射到真实的物理内存上
栈空间:从高地址往低地址存储数据
堆空间:从低地址往高地址存储数据
环境变量:env
内核区:普通用户没有读写权限,可以通过Linux系统调用(系统API函数)对数据进行操作。
文件描述符:
-
PCB:struct
-
文件描述符表:array 默认大小1024
-
前三个位置默认被占用:标准输入、标准输出、标准错误 (默认打开状态)
指向的文件都是当前终端(Linux:一切皆文件)
-
-
一个文件可以被打开fopen多次,但是对应的文件描述符是不同的。
-
每次fopen时,都会自动分配文件描述符表中没有被占用的最小文件描述符。
Linux系统IO函数
标准C库的IO函数和Linux系统IO函数是对应的。
int open (const char *pathname,int flags );
int open (const char *pathname,int flags, mode_t mode);
int close (int fd);
ssize_t read(int fd,void *buf, size_t count);
ssize_t write(int fd,const void *buf, size_t count);
off_t lseek (int fd, off_t offset, int whence);
# Linux系统的IO函数 open函数
man 2 open
# 标准C库的IO函数 (位于手册第三版)
man 3
注意:大部分Linux函数如果调用失败会返回 -1.
open函数:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //函数的声明
#include <unistd.h>
#include <stdio.h>
// 1. 打开一个已经存在的文件
int open(const char *pathname, int flags); // flags 定义成了宏 放在前两个头文件中
参数:
- pathname: 要打开的文件路径
- flags: 对文件的操作权限设置还有其他的设置
- 必选项 O_RDONLY, O_WRONLY, or O_RDWR 这三个设置是互斥的
返回值:返回一个新的文件描述符;如果调用失败,会返回-1.
return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
errno: 属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
#include <stdio.h>
void perror(const char *s);
- s: 用户描述,比如hello,最终的输出是 hello:xxx(实际的错误描述)
作用:打印errno对应的错误描述
int main(){
int fd = open("a.txt", O_RDONLY);
if(fd == -1) perror("open");
// 读写操作
// ...
// 关闭文件(文件描述符,使得它不再指向任何一个文件,并且可重用)
close(fd);
return 0;
}
// 2. 新建并打开一个文件
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname: 要创建的文件路径
- flags: 对文件的操作权限和其他的设置
- 必选项 O_RDONLY, O_WRONLY, or O_RDWR 这三个设置是互斥的
- 可选项 O_CREAT 文件不存在,创建新文件
- mode: 八进制的数,表示用户对创建出的新的文件操作权限。如:0775(0:代表八进制数)
rwx: 111(2) -> 7(8) -wx: 3(8) r--: 4(8)
so -rwxrwxrwx -> 0777(8)
最终的新建文件权限权限:the mode of the created file is (mode & ~umask)
不同用户 umask的值 不同
eg. 0777 (mode) -> 111111111
& 0775 (~umask) -> 111111101
---------------------------
0775 111111101 (按位与:0和任何数都为0)
umask作用:抹去某些权限。
也可以通过 umask 022 人为修改 umask (不过只在当前终端有效)
flags参数是一个int类型的数据,占4个字节,32位。
flags 32个位,每一位都是一个标志位,代表一种情况。
// example
int main(){
// 创建一个新的文件
umask(003);
int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
if(fd == -1) perror("open");
// 关闭文件
close(fd);
return 0;
}
注意:
# 文件权限
boyangcao@MyLinux:~/Linux/Lesson09$ ll
总用量 24
drwxrwxr-x 2 boyangcao boyangcao 4096 7月 21 11:18 ./
drwxrwxr-x 11 boyangcao boyangcao 4096 7月 21 10:49 ../
-rw-rw-r-- 1 boyangcao boyangcao 0 7月 21 11:18 create.c
# 第一个字母代表当前文件的类型
# 后9个字母代表权限 每三个一组
# 第一组:当前用户对文件的权限
# 第二组:当前用户所在的组对文件的权限
# 第三组:其他组对文件的权限
read/write函数
ssize_t read(int fd,void *buf, size_t count);
ssize_t write(int fd,const void *buf, size_t count);
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd: 文件描述符,open()得到的,通过这个文件描述符操作该文件。
- buf: 需要读取数据存放的地方(缓冲区),数组的地址(传出参数)。
- count: 指定的数组大小。
返回值:
- 成功:
>0: 返回实际的读取到的字节数。
=0: 文件已经读取完了。
- 失败: -1 并且设置errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd: 文件描述符,open()得到的,通过这个文件描述符操作该文件。
- buf: 要往磁盘写入的数据,数组的地址。
- count: 要写的数据的实际大小
返回值:
成功:实际写入的字节数
失败:返回-1, 并设置errno。
实际应用:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
// 1. 通过open()打开English.txt文件
int srcfd = open("english.txt", O_RDONLY);
if(srcfd == -1)
{
perror("open");
return -1;
}
// 2. 创建一个新的文件(拷贝文件)
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
if(destfd == -1)
{
perror("open");
return -1;
}
// 3. 频繁的读写操作
char buf[1024] = {0};
int len = 0;
while((len = read(srcfd, buf, sizeof(buf))) > 0) write(destfd, buf, len);
// 4. 关闭文件
close(destfd);
close(srcfd);
return 0;
}
lseek函数
标准C库函数:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
Linux系统函数:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd: 文件描述符,open()得到的,通过这个文件描述符操作该文件。
- offset: 偏移量
- whence:
SEEK_SET: The file offset is set to offset bytes.
设置文件指针的偏移量:第二个参数
SEEK_CUR: The file offset is set to its current location plus offset bytes.
设置偏移量:当前位置 + 第二个参数 offset 的值
SEEK_END: The file offset is set to the size of the file plus offset bytes.
设置偏移量:文件大小 + 第二个参数 offset 的值
返回值:返回文件指针最终所在的位置(offset)。
作用:
1. 移动文件指针到头文件
lseek(fd, 0, SEEK_SET);
2. 获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
3. 获取文件长度
lseek(fd, 0, SEEK_END);
4. 拓展文件的长度: 当前文件10b -> 100b, 增加了100个字节。
lseek(fd, 100, SEEK_END);
注意:在拓展文件长度的时候,在后面需要写入一次数据。
作用:在下载的时候 先提前占据目标大小的空间,再慢慢进行下载替换空数据。
举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd = open("hello.txt", O_RDWR);
if(fd == -1)
{
perror("open");
return -1;
}
// 拓展文件的长度
int ret = lseek(fd, 100, SEEK_END);
if(ret == -1)
{
perror("lseek");
return -1;
}
// 写入一个空数据
write(fd, " ", 1);
// 关闭文件
close(fd);
return 0;
}
stat/lstat函数
stat 结构体:
st_mode: 16位
文件类型在Linux系统中一共有7种。
获取该文件是否有相应权限: 将st_mode 与目标权限做 按位与 操作。
获取该文件类型:st_mode & S_IFMT
(S_IFMT == (2) 1111 0000 0000 0000)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname: 操作的文件路径
- statbuf: 结构体变量,传出参数,用于保存获取到的文件信息。
返回值:
成功:返回0
失败:返回-1, errno将被设置。
int lstat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname: 操作的文件路径
- statbuf: 结构体变量,传出参数,用于保存获取到的文件信息。
返回值:
成功:返回0
失败:返回-1, errno将被设置。
ps. 当有一个软链接
ln -s a.txt b.txt;
stat("b.txt", &statbuf); 获取的是软链接指向的文件的信息
lstat("b.txt", &statbuf); 获取的是软链接本身的信息
举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
struct stat statbuf;
int ret = stat("a.txt", &statbuf);
if(ret == -1)
{
perror("stat");
return -1;
}
printf("size: %ld\n", statbuf.st_size);
return 0;
}
模拟实现 ls -l a.txt 指令
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include <grp.h>
#include <string.h>
// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 boyangcao boyangcao 15 7月 21 15:27 a.txt
int main(int argc, char* argv[])
{
// 判断输入的参数是否正确
if(argc < 2)
{
printf("%s filename\n", argv[0]);
return -1;
}
// 通过stat函数获取用户传入的文件信息
struct stat st;
int ret = stat(argv[1], &st);
if(ret == -1)
{
perror("stat");
return -1;
}
// 获取文件类型和访问权限
char perms[11] = {0}; // 用于保存文件类型和文件权限的字符串
switch(st.st_mode & __S_IFMT)
{
case __S_IFLNK:
perms[0] = 'l';
break;
case __S_IFDIR:
perms[0] = 'd';
break;
case __S_IFREG:
perms[0] = '-';
break;
case __S_IFBLK:
perms[0] = 'b';
break;
case __S_IFCHR:
perms[0] = 'c';
break;
case __S_IFSOCK:
perms[0] = 's';
break;
case __S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 判断文件的访问权限
// 文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
// 获取硬链接数
int linkNum = st.st_nlink;
// 文件所有者
char * fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所在组
char * fileGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
long int fileSize = st.st_size;
// 获取修改时间
char* time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time) - 1);
char buf[1024];
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n", buf);
return 0;
}
文件属性操作函数
// 判断文件权限/是否存在
int access ( const char *pathname,int mode);
// 修改文件权限
int chmod ( const char *filename,int mode);
// 修改文件所有者/所在组
int chown (const char *path,uid_t owner, gid_t group);
// 缩减、扩展文件大小
int truncate (const char *path, off_t length);
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
作用:修改文件的权限
参数:
- pathname: 需要修改的文件路径
- mode: 需要修改的权限值,八进制的数
返回值:
成功:返回0
失败:返回-1
int ret = chmod("a.txt", 0775);
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或扩展文件的尺寸至指定的大小
参数:
- path: 需要修改文件的路径
- length: 需要最终文件变成的大小
返回值:成功返回0 ,失败返回-1
int ret = truncate("b.txt", 5);
目录操作函数
// 建立目录
int mkdir (const char *pathname,mode_t mode);
// 删除目录(只能删除空目录)
int rmdir (const char *pathname);
// 对目录重命名
int rename (const char *oldpath,const char *newpath);
// 更改当前工作目录
int chdir (const char *path);
// 获取当前工作路径
char *getcwd (char *buf,size_t size);
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
- pathname: 创建目录的路径
- mode: 权限,八进制的数
返回值: 成功返回0,失败返回-1
int ret = mkdir("aaa", 0777);
#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录
比如在/home/nowcoder 启动了一个可执行程序,进程的工作目录 /home/nowcoder
参数:
path: 需要修改的工作目录
int ret = chdir("/home/boyangcao/Linux/Lesson13");
#include <unistd.h>
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf: 存储的路径,指向一个数组(传出参数)。
- size: 指定的数组大小。
返回值:
返回的是指向的一块内存,这个数据就是第一个参数。
getcwd(buf, sizeof(buf));
目录遍历函数
// 打开一个目录
DIR *opendir ( const char *name);
// 读取一个目录
struct dirent *readdir (DIR *dirp);
// 关闭一个目录
int closedir (DIR *dirp);
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
作用:打开一个目录
参数:
-name: 需要打开的目录的名称
返回值:
DIR * 类型: 理解为目录流信息
错误返回NULL
DIR * dir = opendir(path);
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
作用:读取目录中的数据
参数:
- dirp: 通过opendir返回的结果
返回值:
struct dirent, 代表读取到的信息
读取到了末尾或者失败了,返回NULL
struct dirent *ptr = readdir(dir);
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
作用:关闭目录
参数:
- dirp: 通过opendir返回的结果
closedir(dir);
dirent结构体和d_type:
注意:
DT_REG
不是ISO C11扩展的一部分。设置-std=c11
仅严格启用C11标准中定义的功能。
可以使用功能宏启用其他扩展。正如readdir手册提到的,您需要_DEFAULT_SOURCE
宏来启用文件类型常量。
在包含dirent.h
之前,可以在源代码中执行此操作
#define _DEFAULT_SOURCE
#include <dirent.h>
或通过命令行作为编译器选项
cc -std=c11 -D_DEFAULT_SOURCE -Wall -Werror -pedantic
示例:读取目录下所有普通文件的个数:
#define _DEFAULT_SOURCE
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getFileNum(const char * path);
// 读取某个目录下所有普通文件的个数
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("%s path\n", argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件的个数: %d\n", num);
return 0;
}
// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path)
{
//1. 打开目录
DIR * dir = opendir(path);
if(dir == NULL)
{
perror("opendir");
return 0;
}
// 记录普通文件的个数
int total = 0;
struct dirent *ptr;
while((ptr = readdir(dir)) != NULL)
{
// 获取名称
char* dname = ptr->d_name;
// 忽略掉. 和 ..
if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) continue;
// 判断是否是普通文件还是目录
if(ptr->d_type == DT_DIR)
{
//目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG)
{
// 普通文件
total++;
}
}
// 关闭目录
closedir(dir);
return total;
}
dup/dup2函数
int dup (int oldfd); //复制文件描述符
int dup2 (int oldfd, int newfd) ; //重定向文件描述符
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符,并和oldfd指向同一个文件
fd = 3; fd1 = dup(fd)
fd指向的是1.txt,则fd1也是指向1.txt
从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符。
int fd1 = dup(fd);
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符。
假如oldfd 指向a.txt, newfd 指向b.txt
调用函数成功后:newfd 和 b.txt 做close,newfd 指向了 a.txt
oldfd 必须是一个有效的文件描述符
oldfd 和 newfd 值相同,相当于什么也没有做。
返回值:On success, these system calls return the new file descriptor.
On error, -1 is returned, and errno is set appropriately.
int fd2 = dup2(fd, fd1);
fcntl函数
// 1. 复制文件描述符
// 2. 设置/获取文件的状态标志
int fcntl (int fd, int cmd,... /* arg */ );
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... \* arg *\);
参数:
fd: 需要操作的文件描述符。
cmd: 表示对文件描述符如何操作
- F_DUPFD:复制文件描述符, 复制的是第一个参数fd,得到一个新的文件描述符。
int ret = fcntl(fd, F_DUPFD);
- F_GETFL: 获取指定的文件描述符的文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西。
- F_SETFL: 设置文件描述符文件状态flag
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选项:O_APPEND, O_NONBLOCK
O_APPEND 表示追加数据
O_NONBLOCK 设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为
// 获取文件描述符状态的flag
int flag = fcntl(fd, F_GETFL);
flag |= O_APPEND;
// 修改文件描述符状态的flag,给flag加入O_APPEND标记
int ret = fcntl(fd, F_SETFL, flag);