文章目录
- 1 概述
- 2 文件描述符
- 3 文件I/O操作
- 3.1 打开文件操作
- 3.2 关闭文件操作
- 3.3 向文件写入数据
- 3.4 从文件读取数据
- 4 给文件描述符添加非阻塞特性
- 4.1 当此文件描述符不存在
- 4.2 当此文件描述符存在
- 5 获取文件状态信息
- 6 文件目录操作
- 6.1 打开目录操作
- 6.2 读取目录信息
- 6.3 关闭目录
- 6.4 参考示例:扫描目录
1 概述
系统调用:操作系统提供给用户函数调用的一组特殊接口。
2 文件描述符
文件描述符是通过存储在内核区的文件描述符表(位图)管理。
功能 | 命令 |
---|---|
查看进程号 | ps -A | grep 可执行文件名 |
查看打开的文件描述符 | ls /proc/进程号/fd |
查看系统限制 | ulimit -a |
修改最大存储文件描述符空间大小 (当前终端有效) | ulimit -n 1024 |
查看系统掩码 | umask |
修改系统掩码 | umask mode |
查看各组用户的默认权限 | umask -S |
系统默认为每个进程打开的文件描述符。
文件描述符 | 文件 | 设备 |
---|---|---|
0 | 标准输入设备 | 键盘 |
1 | 标准输出设备 | 屏幕 |
2 | 标准错误输出设备 | 屏幕 |
3 文件I/O操作
3.1 打开文件操作
open - 打开并可能创建一个文件
open() 系统调用打开路径名指定的文件。如果指定的文件有,如果不存在,则可以选择通过 open() 创建它(如果在标志中指定了 O_CREAT)。
概要
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数
pathname - 文件路径名
flags - 文件操作标志
mode - 文件创建权限
返回值
成功返回新的文件描述符(非负整数),
或者如果发生错误,则为 -1(在这种情况下,会适当设置 errno)。
常用文件操作标志:
标志 | 含义 |
---|---|
O_RDONLY | 只读方式打开文件。 |
O_WRONLY | 只写方式打开文件。 |
O_RDWR | 可读可写的方式打开文件。 |
O_CREAT | 如果路径名不存在,将其创建为常规文件。 |
O_EXCL | 确保此调用创建文件: 如果结合指定此标志,使用 O_CREAT ,并且路径名已经存在,则 open() 失败并出现错误 EEXIST 。 |
O_TRUNC | 如果文件已经存在并且是常规文件并且访问模式允许写入(即是 O_RDWR 或 O_WRONLY )它将被截断为长度 0。如果文件是 FIFO 或终端设备文件,则忽略 O_TRUNC 标志。 |
O_APPEND | 文件以附加方式打开,文件流指针指向文件末尾。 |
O_NONBLOCK | 文件以非阻塞模式打开。 |
为mode
提供的符号常量(与O_CREAT
配套使用):
符号常量 | 值 | 含义 |
---|---|---|
S_IRWXU | 0700 | 文件所有者具有读、写、执行权限。 |
S_IRUSR | 0400 | 文件所有者有读权限。 |
S_IWUSR | 0200 | 文件所有者有写权限。 |
S_IXUSR | 0100 | 文件所有者有执行权限。 |
S_IRWXG | 0070 | 同组用户具有读、写、执行权限。 |
S_IRGRP | 0040 | 同组用户有读权限。 |
S_IWGRP | 0020 | 同组用户有写权限。 |
S_IXGRP | 0010 | 同组用户有执行权限。 |
S_IRWXO | 0007 | 其他用户具有读、写、执行权限。 |
S_IROTH | 0004 | 其他用户有读权限。 |
S_IWOTH | 0002 | 其他用户有写权限。 |
S_IXOTH | 0001 | 其他用户有执行权限。 |
创建的文件权限为:mode & ~umask
。
示例1:打开已经存在的文件并清空文件内容。
方法一:使用库函数操作。
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *fp = NULL;
// 打开文件操作
// w+ == O_RDWR | O_CREAT | O_TRUNC
fp = fopen("test", "w+");
if (NULL == fp)
{
perror("fopen");
return -1;
} /* end of if (NULL == fp) */
// 关闭文件操作
fclose(fp);
return 0;
}
方法二:使用系统调用操作。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd = 0;
// 打开文件
fd = open("test", O_WRONLY | O_TRUNC);
if (-1 == fd)
{
perror("open");
return -1;
} /* end of if (-1 == fd) */
// 打印文件描述符
printf("fd = %d\n", fd); // 3
// 关闭文件
close(fd);
return 0;
}
示例2:打开不存在文件,文件权限为0741
。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd = 0;
// 创建文件并设置权限
fd = open("test1", O_CREAT | O_RDWR, S_IRWXU | S_IRGRP | S_IXOTH);
if (-1 == fd)
{
perror("open");
return -1;
} /* end of if (-1 == fd) */
printf("fd: %d\n", fd);
// 关闭文件
close(fd);
return 0;
}
3.2 关闭文件操作
close - 关闭文件描述符
close() 关闭文件描述符,使其不再引用任何文件,并且可以被重用。
与其关联的文件上持有的任何记录锁(请参阅 fcntl(2))进程所拥有的和所拥有的都将被删除(无论用于获取锁文件描述符是什么)。
概要
#include <unistd.h>
int close(int fd);
参数
fd - 文件描述符
返回值
close() 成功时返回零。出错时,返回 -1,并设置适当的 errno。
注意
成功关闭并不能保证数据已成功保存到磁盘,因为内核使用缓冲区高速缓存来延迟写入。
通常,文件系统文件关闭时不刷新缓冲区。
如果您需要确保数据物理存储在底层磁盘上,请使用 fsync(2)。
示例:验证成功关闭文件描述符的返回值。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd = 0;
int ret = -1;
fd = open("test", O_CREAT | O_RDWR, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open");
return -1;
} /* end of if (-1 == fd) */
printf("fd: %d\n", fd);
ret = close(fd);
printf("ret: %d\n", ret);
return 0;
}
$ ./a.out
fd: 3
ret: 0
3.3 向文件写入数据
write - 写入文件描述符
write() 从 buf 开始的缓冲区中写入 count 个字节到由文件描述符 fd 引用的文件。
概要
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数
fd - 文件描述符
buf - 缓冲区
count - 字节数
返回值
成功时,返回写入的字节数。
出错时,返回-1,并且设置 errno 来指示错误原因。
示例:将数据写入文件中。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
int ret = -1;
// 打开文件描述符
int fd = open("test", O_CREAT | O_TRUNC | O_RDWR, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open");
return -1;
} /* end of if (-1 == fd) */
printf("%d\n", fd);
// 写数据
ret = write(fd, "hello world", 5);
if (-1 == ret)
{
perror("write");
return -1;
} /* end of if (-1 == ret) */
// 关闭文件描述符
close(fd);
return 0;
}
$ ./a.out
3
$ cat test
hello
3.4 从文件读取数据
read - 从文件描述符中读取
read() 尝试从文件描述符 fd 中读取最多 count 个字节到缓冲区中从 buf 开始。
概要
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数
fd - 文件描述符
buf - 缓冲区
count - 字节数
返回值
成功时,返回读取的字节数;
出错时,返回 -1,并适当设置 errno。
4 给文件描述符添加非阻塞特性
4.1 当此文件描述符不存在
当此文件描述符不存在,我们只需要以非阻塞方式打开文件描述符。
open(FILE_PATH, O_WRONLY | O_NONBLOCK | CREAT, 0744);
4.2 当此文件描述符存在
使用fcntl()
修改文件描述符状态标记。
fcntl - 操作文件描述符
概要
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数
fd - 文件描述符
cmd - 指令
... - 变参
返回值
成功时根据不同的cmd返回不同的值;
出错时,返回 -1,并适当设置 errno。
修改状态标记相关的cmd
:
命令 | 含义 | arg |
---|---|---|
F_GETFL | 获取文件访问模式和文件状态。 | 被忽略。 |
F_SETFL | 将文件状态标志设置为 arg 指定的值。 | 文件状态标记 |
注意:
每个打开的文件描述都有一定的关联状态标志,由open(2)
并可能由 fcntl()
修改。重复的文件描述符(由dup(2)、fcntl(F_DUPFD)、fork(2)
等)引用相同的打开文件描述,并且因此共享相同的文件状态标志。
文件存取方式(O_RDONLY
、O_WRONLY
、O_RDWR
)和文件创建标志(即 O_CREAT
、O_EXCL
、arg
中的 O_NOCTTY、O_TRUNC
)将被忽略。在 Linux
上,此命令可以更改仅 O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME
和 O_NONBLOCK
标志。它无法更改 O_DSYNC
和 O_SYNC
标志。
使用:将文件描述符添加非阻塞特性。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd = 0;
int flag = 0;
int ret = 0;
// 打开文件描述符
fd = open("test", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open");
return -1;
} /* end of if (-1 == fd) */
// 获取文件描述符的状态标记
flag = fcntl(fd, F_GETFL);
if (-1 == flag)
{
perror("fcntl");
return -1;
} /* end of if (-1 == flag) */
// 添加阻塞特性
ret = fcntl(fd, F_SETFL, flag | O_NONBLOCK);
if (-1 == ret)
{
perror("fcntl");
} /* end of if (-1 == ret) */
// 关闭文件描述符
close(fd);
return 0;
}
5 获取文件状态信息
stat、lstat - 获取文件状态
这些函数返回有关文件的信息,位于 stat 指向的缓冲区中缓冲区。
文件本身不需要任何权限,但是对于 stat() 来说,fstatat() 和 lstat()——所有直接命令都需要执行(搜索)权限通向该文件的路径名中的内容。
概要
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
参数
pathname - 文件路径名
statbuf - 数据存储缓冲区地址
返回值
成功后,返回零。出错时,返回 -1,并适当设置 errno。
状态统计结构体参考结构:
struct stat
{
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
结构体成员解析:
成员 | 含义 |
---|---|
st_ino | 该字段包含文件的索引节点号。 |
st_mode | 该字段包含文件类型和模式。有关详细信息,请参阅 inode(7)。 |
st_nlink | 该字段包含文件的硬链接数。 |
st_uid | 该字段包含文件所有者的用户 ID。 |
st_gid | 该字段包含文件的组所有者的 ID。 |
st_size | 该字段给出文件的大小(如果它是常规文件或符号文件链接)以字节为单位。 符号链接的大小是它包含路径名的长度,但没有终止空字节。 |
st_atime | 这是最后一次访问文件数据的时间。 |
st_mtime | 这是文件数据最后一次修改的时间。 |
st_ctime | 这是文件的上次状态更改时间戳(上次更改的时间索引节点)。 |
拓展函数:ctime()
ctime - 将日期和时间转换为细分时间或 ASCII
ctime()函数都采用数据类型的参数time_t,表示日历时间。
当解释为绝对时间时值,它表示自纪元 1970-01-01 以来经过的秒数00:00:00 +0000(世界标准时间)。
概要
#include <time.h>
char *ctime(const time_t *timep);
参数
timep - 指向时间类型的指针
返回值
成功时,ctime() 返回指向字符串的指针。
错误时为 NULL。发生错误时,会设置 errno 以指示错误原因。
参考示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char **argv)
{
struct stat sb;
int ret = 0;
// 获取文件状态
ret = stat("test", &sb);
if (-1 == ret)
{
perror("stat");
return -1;
} /* end of if (-1 == ret) */
// 解析获取到的信息
// 1.文件索引节点号
printf("%lu\n", sb.st_ino);
// 2.文件的硬链接数
printf("%lu\n", sb.st_nlink);
// 3.文件类型
#if 0
if (S_IFREG & sb.st_mode)
{
printf("普通文件\n");
}
else if (S_IFDIR & sb.st_mode)
{
printf("目录\n");
}
else if (S_IFLNK & sb.st_mode)
{
printf("符号链接\n");
}
else if (S_IFCHR & sb.st_mode)
{
printf("字符设备\n");
}
else if (S_IFBLK & sb.st_mode)
{
printf("块设备\n");
}
else if (S_IFSOCK & sb.st_mode)
{
printf("套接字");
}
else if (S_IFIFO & sb.st_mode)
{
printf("管道\n");
}
#else
if (S_ISREG(sb.st_mode))
{
printf("普通文件\n");
}
else if (S_ISDIR(sb.st_mode))
{
printf("目录\n");
}
else if (S_ISLNK(sb.st_mode))
{
printf("符号链接\n");
}
else if (S_ISCHR(sb.st_mode))
{
printf("字符设备\n");
}
else if (S_ISBLK(sb.st_mode))
{
printf("块设备\n");
}
else if (S_ISSOCK(sb.st_mode))
{
printf("套接字");
}
else if (S_ISFIFO(sb.st_mode))
{
printf("管道\n");
}
#endif
// 4.文件所有者的用户ID和组ID
printf("uid:%u gid:%u\n", sb.st_uid, sb.st_gid);
// 5.文件的大小
printf("size: %luB\n", sb.st_size);
// 6.文件的最后一次数据访问时间
printf("atime: %s", ctime(&sb.st_atime));
// 7.文件的最后一次数据修改时间
printf("mtime: %s", ctime(&sb.st_mtime));
// 8.文件状态更改时间戳
printf("ctime: %s", ctime(&sb.st_ctime));
return 0;
}
$ ./a.out
2631317
1
普通文件
uid:1000 gid:1000
size: 5B
atime: Wed Mar 13 13:21:04 2024
mtime: Wed Mar 13 13:21:00 2024
ctime: Wed Mar 13 13:21:00 2024
6 文件目录操作
6.1 打开目录操作
opendir - 打开目录
opendir()函数打开目录对应的目录流name,并返回指向目录流的指针。该流位于目录中的第一个条目。
概要
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数
name - 目录名
返回值
函数返回指向目录流的指针。
出错时,返回 NULL,并适当设置 errno。
6.2 读取目录信息
readdir - 读取目录
readdir() 函数返回一个指向 dirent 结构的指针,该结构表示dirp 指向的目录流中的下一个目录条目。
它返回 NULL到达目录流末尾或发生错误时。
概要
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数
dirp - 目录句柄
返回值
成功时,readdir() 返回指向 dirent 结构的指针。 (这个结构是静态分配;不要尝试free(3)。)
如果到达目录流末尾,则返回 NULL 并且不返回 errno改变了。如果发生错误,则返回 NULL 并适当设置 errno。
到区分流结束和错误,在调用 readdir() 之前将 errno 设置为零,然后检查 errno 的值,如果返回 NULL。
struct dirent
结构体定义:
struct dirent
{
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
为d_type
定义的宏:
宏 | 含义 |
---|---|
DT_REG | 常规文件 |
DT_DIR | 目录 |
DT_CHR | 字符设备 |
DT_BLK | 块设备 |
DT_LNK | 符号链接 |
DT_SOCK | 套接字 |
DT_FIFO | 管道 |
DT_UNKNOWN | 无法确定文件类型 |
6.3 关闭目录
closedir - 关闭目录
Closedir() 函数关闭与 Dirp 关联的目录流。一个成功的
成功调用 Closedir() 也会关闭与Dirp关联的底层文件描述符。目录流描述符 dirp 在此调用后不可用。
概要
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
参数
dirp - 目录流
返回值
closeir() 函数成功时返回 0。
出错时返回-1,并返回errno被适当地设置。
6.4 参考示例:扫描目录
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
int main(int argc, char **argv)
{
DIR *dir = NULL;
struct dirent *dp = NULL;
int cnt = 0;
// 打开目录
dir = opendir("./");
if (NULL == dir)
{
perror("opendir");
return -1;
} /* end of if (NULL == dir) */
// 逐个读取目录流
errno = 0;
cnt = 0;
while (1)
{
dp = readdir(dir);
if (0 == errno && NULL == dp)
{
// 目录扫描结束
break;
}
else if (0 != errno && NULL == dp)
{
// 读取出错
perror("readdir");
return -2;
}
cnt++;
// 解析信息
printf("file%d: %s\n", cnt, dp->d_name);
printf("类型: ");
if (DT_REG == dp->d_type)
{
printf("普通文件\n");
}
else if (DT_DIR == dp->d_type)
{
printf("目录\n");
}
else if (DT_LNK == dp->d_type)
{
printf("符号链接\n");
}
else if (DT_CHR == dp->d_type)
{
printf("字符设备\n");
}
else if (DT_BLK == dp->d_type)
{
printf("块设备\n");
}
else if (DT_SOCK == dp->d_type)
{
printf("套接字\n");
}
else if (DT_FIFO == dp->d_type)
{
printf("管道\n");
}
else if (DT_UNKNOWN == dp->d_type)
{
printf("未知\n");
}
} /* end of while (1) */
closedir(dir);
return 0;
}
$ ./a.out
file1: test
类型: 普通文件
file2: 02code.c
类型: 普通文件
file3: tes1
类型: 普通文件
file4: 05code.c
类型: 普通文件
file5: 01code.c
类型: 普通文件
file6: 07code.c
类型: 普通文件
file7: 06code.c
类型: 普通文件
file8: 08code.c
类型: 普通文件
file9: 04code.c
类型: 普通文件
file10: 03code.c
类型: 普通文件
file11: ..
类型: 目录
file12: a.out
类型: 普通文件
file13: .
类型: 目录
file14: test1
类型: 普通文件