Linux系统编程(六)文件IO

6.1 文件 IO

  在内存角度,I (Input)指将文件从磁盘中读入内存,O (Output) 指将文件从内存中写入磁盘。在 C 语言中,对于文件的操作有如下 API
在这里插入图片描述
  C 语言中的文件处理函数库看做第三方库,在不同平台,会调用底层的文件处理函数,所以是跨平台的,并且其通过缓冲区实现,更加高效易用。下面介绍的 Linux 文件处理函数,是在 Linux 平台下的函数。

FILE* 简单说明:

  • 文件描述符 :文件 ID,用于指向文件的唯一标识符
  • 文件读写指针位置 :维护读写指针,用于读取数据和写数据
  • I/O缓冲区(内存地址):内存与磁盘之间IO操作时,通过缓冲区来进行交换,当缓冲区满时,进行写操作,减少IO次数,使得读写效率高。缓冲区默认大小为 8 K。

  在网络通信中,实时性有着较高的要求,所以需要Linux 系统的IO函数,对磁盘的文件进行IO时,使用标准C 库中的 IO效率更高。标注 C 库与 Linux 系统的IO函数的关系(调用与被调用)如下:
C与Linux

6.2 虚拟地址空间

  进程是系统运行程序运行分配资源最小单位。程序是在磁盘中的代码,而进程是运行的程序,加载至内存中的程序。每个进程加载至内存中,其可以表示为虚拟地址空间,其大小由CPU决定,32位系统,地址空间大小为 2 32 2^{32} 232 (4G),64为系统一般为 2 48 2^{48} 248
虚拟地址空间
  以32位系统为例,虚拟地址空间 分为用户区和内核区,用户区为0~3G,内核区为3-4G。通过内存管理单元(MMU)映射物理内存

扩展:虚拟内存管理技术,MMU如何映射内存。

6.3 文件描述符

  进程对文件进行操作时,通过内核区的进程控制块(PCB,结构体)中保存的文件描述符表对文件进行管理。
文件描述符表
  文件描述符表默认大小为1024,即每个进程默认打开的文件个数为1024个。其中,前三个为固定的文件,标准输入(stdin),标准输出(stdout),标准错误(stderror),三个文件指向当前终端,所以进程输入输出默认为终端。

当一个文件被多次打开时,其文件描述符不同。

6.4 open 打开文件

在终端通过命令查看open 文档,open 为 Linux系统函数(第二章),

man 2 open
  1. 使用open 所需要的包含头文件
#include <sys/types.h>		// 参数 flags 所需要的宏在该文件中定义 
#include <sys/stat.h>		
#include <fcntl.h>			// 函数声明
  1. 函数接口
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
  1. 参数
  • pathname :需要打开的文件路径
  • flags :对文件操作权限的设置及其他设置。O_RDONLY, O_WRONLY, O_RDWR,三个操作互斥,仅能选取一个。
  1. 返回值
  • 调用成功,返回文件描述符
  • 调用失败,返回 -1,并设置 errorno

errorno 是系统函数库中一个全局变量,记录错误号,且是最近的错误号。使用方式:

void perror(const char *s);

用于打印errorno 对应的错误信息。参数 s 为用户指定的错误描述,输出是格式为 [用户指定的错误描述] : 实际错误信息。

perror打印错误信息, 简单示例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(){

    int fd = open("a.txt", O_RDONLY);
    if (fd == -1){
        perror("open failed");
    }

    close(fd);

    return 0;
}

编译结果如下,输出错误信息
perror

6.5 open 创建一个新文件

  1. 包含头文件
#include <sys/types.h>		// 参数 flags 所需要的宏在该文件中定义 
#include <sys/stat.h>		
#include <fcntl.h>			// 函数声明
  1. 函数接口
// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
  1. 参数
  • 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 & ~umask

umask 作用为抹去某些权限,使得文件权限更加合理。如 0002 使得其他用户对文件写权限抹去。

  1. 返回值
  • 调用成功,返回文件描述符
  • 调用失败,返回 -1,并设置 errorno
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(){

    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
    if (fd == -1){
        perror("open failed");
    }

    close(fd);

    return 0;
}

编译示例,创建新文件
create

6.7 read-读取文件 write-写入函数

  1. 包含头文件
#include <unistd.h>
  1. 函数原型
ssize_t read(int fd, void *buf, size_t count);
  1. 参数
  • fd :文件描述符,open 得到,通过文件描述符操作文件
  • buffer :传出参数,需要读取的数据存放的 buffer 地址
  • count :指定的数组大小
  1. 返回值
  • 读取成功
    • 大于 0表示 返回读取的字节数
    • 等于0 表示已经读取至文件结尾
  • 读取失败,返回 -1,设置 error

write 函数:

  1. 包含头文件
#include <unistd.h>
  1. 函数原型
ssize_t write(int fd, const void *buf, size_t count);
  1. 参数
  • fd :文件描述符,open 得到,通过文件描述符操作文件
  • buffer :要往磁盘写入的数据,数组
  • count :要写的数据的实际大小
  1. 返回值
  • 写入成功,返回实际写入的字节数
  • 写入失败,返回-1,并设置 errorno

拷贝文件示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>


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;
}

编译运行
copyfile

6.8 lseek 文件位置偏移

标准C库中 fseek 对应系统库函数 lseek

  1. 包含头文件
#include <sys/types.h>
#include <unistd.h>
  1. 函数原型
off_t lseek(int fd, off_t offset, int whence);
  1. 参数
  • fd :文件描述符,通过 open 得到,使用 fd 操作文件
  • offset :偏移量
  • whence :对文件指偏移 offset
    • SEEK_SET:设置文件偏移量:offset
    • SEEK_CUR:设置偏移量:从当前位置 + offset
    • SEEK_END:设置偏移量:文件大小 + offset
      作用:
      (1)移动文件指针至文件开头:lseek(fd, 0, SEEK_SET);
      (2)获取当前文件指针位置:lseek(fd, 0, SEEK_CUR);
      (3)获取文件长度:lseek(fd, 0, SEEK_END);
      (4)扩展文件长度: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;
}

编译执行,文件大小从 0 扩展为 101
lseek

6.8 stat lstat 获取文件相关信息

  1. 包含头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
  1. 函数原型
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
  1. 参数
  • 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; 		// 最后一次改变时间(指属性)
};
  1. 返回值
  • 成功调用时,返回0
  • 失败返回 -1 ,设置 errorno

stmode

编译执行
stat

6.9 实现 ls -l

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <pwd.h>
#include <grp.h>
#include <time.h>

#include <string.h>

// 模拟实现 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;
}

ls-l
  

点关注,不迷路。一键三连是对我最大的支持,欢迎关注编程小镇,每天涨一点新姿势😄。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值