在内存角度,I
(Input)指将文件从磁盘中读入内存,O
(Output) 指将文件从内存中写入磁盘。在 C
语言中,对于文件的操作有如下 API
。
C 语言中的文件处理函数库看做第三方库,在不同平台,会调用底层的文件处理函数,所以是跨平台的,并且其通过缓冲区实现,更加高效易用。下面介绍的 Linux
文件处理函数,是在 Linux
平台下的函数。
对 FILE*
简单说明:
在网络通信中,实时性有着较高的要求,所以需要Linux 系统的IO函数,对磁盘的文件进行IO时,使用标准C
库中的 IO效率更高。标注 C
库与 Linux 系统的IO函数的关系(调用与被调用)如下:
进程是系统运行程序运行分配资源最小单位。程序是在磁盘中的代码,而进程是运行的程序,加载至内存中的程序。每个进程加载至内存中,其可以表示为虚拟地址空间,其大小由CPU决定,32位系统,地址空间大小为 2 32 2^{32} 232 (4G),64为系统一般为 2 48 2^{48} 248 。
以32位系统为例,虚拟地址空间 分为用户区和内核区,用户区为0~3G,内核区为3-4G。通过内存管理单元(MMU)映射 至 物理内存。
扩展:虚拟内存管理技术,MMU如何映射内存。
进程对文件进行操作时,通过内核区的进程控制块(PCB,结构体)中保存的文件描述符表对文件进行管理。
文件描述符表默认大小为1024,即每个进程默认打开的文件个数为1024个。其中,前三个为固定的文件,标准输入(stdin
),标准输出(stdout
),标准错误(stderror
),三个文件指向当前终端,所以进程输入输出默认为终端。
当一个文件被多次打开时,其文件描述符不同。
在终端通过命令查看open
文档,open
为 Linux系统函数(第二章),
man 2 open
open
所需要的包含头文件#include // 参数 flags 所需要的宏在该文件中定义
#include
#include // 函数声明
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
pathname
:需要打开的文件路径flags
:对文件操作权限的设置及其他设置。O_RDONLY, O_WRONLY, O_RDWR,三个操作互斥,仅能选取一个。-1
,并设置 errorno
。
errorno
是系统函数库中一个全局变量,记录错误号,且是最近的错误号。使用方式:
void perror(const char *s);
用于打印
errorno
对应的错误信息。参数s
为用户指定的错误描述,输出是格式为 [用户指定的错误描述] : 实际错误信息。
perror
打印错误信息, 简单示例
#include
#include
#include
#include
int main(){
int fd = open("a.txt", O_RDONLY);
if (fd == -1){
perror("open failed");
}
close(fd);
return 0;
}
#include // 参数 flags 所需要的宏在该文件中定义
#include
#include // 函数声明
// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
pathname
:需要创建文件的路径flags
:对文件操作权限的设置及其他设置
O_RDONLY
, O_WRONLY
, O_RDWR
,三个操作互斥,仅能选取一个。O_CLOEXEC
,O_CREAT
, O_DIRECTORY
, O_EXCL
, O_NOCTTY
, O_NOFOLLOW
, O_TMPFILE
, O_TRUNC
mode
:八进制数,表示用户对创建的新文件的操作权限,计算方式:mode = mode & ~umaskumask 作用为抹去某些权限,使得文件权限更加合理。如 0002 使得其他用户对文件写权限抹去。
-1
,并设置 errorno
。#include
#include
#include
#include
int main(){
int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
if (fd == -1){
perror("open failed");
}
close(fd);
return 0;
}
#include
ssize_t read(int fd, void *buf, size_t count);
fd
:文件描述符,open
得到,通过文件描述符操作文件buffer
:传出参数,需要读取的数据存放的 buffer 地址count
:指定的数组大小0
表示 返回读取的字节数0
表示已经读取至文件结尾-1
,设置 error
。write 函数:
#include
ssize_t write(int fd, const void *buf, size_t count);
fd
:文件描述符,open
得到,通过文件描述符操作文件buffer
:要往磁盘写入的数据,数组count
:要写的数据的实际大小errorno
拷贝文件示例:
#include
#include
#include
#include
#include
int main(){
// 1. 通过 open 打开文件得到文件描述符
int srcfd =open("create.c", O_RDONLY);
if (srcfd == -1){
perror("open");
return -1;
}
// 2. 创建一个新文件
int destfd = open("create_copy.c", O_WRONLY | O_CREAT, 0664);
if (destfd == -1){
perror("open");
return -1;
}
// 3. 频繁读写操作
char buffer[1024] = {
0};
int len = 0;
while ((len = read(srcfd, buffer, sizeof(buffer)) > 0)){
write(destfd, buffer, len);
}
// 4. 关闭文件
close(destfd);
close(srcfd);
return 0;
}
标准C库中 fseek
对应系统库函数 lseek
#include
#include
off_t lseek(int fd, off_t offset, int whence);
fd
:文件描述符,通过 open
得到,使用 fd
操作文件offset
:偏移量whence
:对文件指偏移 offset
offset
offset
offset
lseek(fd, 0, SEEK_SET);
lseek(fd, 0, SEEK_CUR);
lseek(fd, 0, SEEK_END);
lseek(fd, 100, SEEK_END);
扩展文件示例
#include
#include
#include
#include
#include
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;
}
#include
#include
#include
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
pathname
:文件路径statbuf
:传出参数,结构体变量,用于保存获取的信息statbuf 结构体如下
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指属性)
};
#include
#include
#include
#include
#include
#include
#include
#include
#include
// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 ysz ysz 101 Dec 21 20:13 hello.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] = '1';
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;
}
点关注,不迷路。一键三连是对我最大的支持,欢迎关注编程小镇,每天涨一点新姿势。