Linux:基础IO

上篇文章:Linux:自主shell命令行解释器附源码

相关文章:c语言:文件操作


目录

1.理解"文件"

1.1文件操作的归类认知

思考

1.2侠义理解

1.3广义理解

1.4系统角度

拓展

2.C文件操作

2.1打开log.txt文件

2.1.1fopen

2.1.2fclose

2.2对文件做写入

2.2.1fwrite

2.2.2fputs

2.2.3fprintf

2.3fopen各个模式以及拓展

2.3.1w模式

2.3.2输出重定向

2.3.3getcwd

2.3.4chdir

2.3.5a模式

2.3.6追加重定向

2.4读

2.4.1fread

2.4.2fgets

2.4.3fscanf

2.4.4feof

2.5对读和写的理解

3.系统级别的文件操作

3.1学习系统调用

open

位图传参

文件描述符fd

3.2代码

3.2.1产生与fopen中w模式相同的效果

3.2.2产生与fopen中a模式相同的效果


1.理解"文件"

1.1文件操作的归类认知

对于0KB的空文件是占用磁盘空间的。

文件是文件属性(元数据)+文件内容的集合(文件 = 属性(如fstat)(元数据) + 内容(如fread/fwrite))。

所有的文件操作本质是文件内容操作和文件属性操作

思考

操作文件之前需要打开文件(fopen)

1.打开文件的本质是什么? 是把文件加载到内存(包含部分属性和内容)

2.为什么需要先打开文件? 由冯诺依曼体系结构规定

3.由谁打开? 代码示例:FILE *fp = fopen(); fwrite(fp......); 当我们输入这些代码时文件并没有被打开,而是通过编译,转换为exe文件,并且代码运行起来时由进程打开了文件!我们所说的malloc是动态内存分配,就是指在程序运行期间由内存分配大小。

所以学习对文件的操作本质是学习进程和文件的关系

1.2侠义理解

文件在磁盘中,磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的。

磁盘是外设(既是输出设备也是输入设备)

磁盘上的文件,本质是对文件的所有操作,都是对外设的输入和输出,简称IO(Input,Output),其中输入输出的含义是站在内存的视角定义的。

1.3广义理解

Linux下一切皆文件(键盘、显示器、网卡、磁盘......这些都是抽象化的过程)

1.4系统角度

对于文件的操作本质是进程对文件的操作

磁盘的管理者是操作系统

文件的读写本质不是通过C语言/C++的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的。

拓展

磁盘文件分为被进程打开的文件和没有被进程打开的文件。

其中内存中的打开文件有关于进程和文件的关系上,磁盘上没有被打开的文件在文件系统上。

磁盘是硬件,访问文件就是在访问磁盘。访问硬件需要操作系统和磁盘的驱动,操作系统必须提供系统调用来访问文件(本质就是访问硬盘),而我们在使用C语言时之所以没有使用系统调用的感受是因为C语言帮我们封装了系统调用!!!

2.C文件操作

2.1打开log.txt文件

myfile.c:

复制代码
int main()
{
    FILE *fp = fopen("log.txt", "w");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    fclose(fp);
    return 0;
}

其中:

cwd:指向当前进程运行目录的一个符号链接。

exe:指向启动当前进程的可执行文件(完整路径)的符号链接。

打开文件,本质是进程打开,所以,进程知道自己在哪里,即便文件不带路径,进程也知道。由此,OS就能知道要创建的文件放在哪里了。

2.1.1fopen

其中:

FILE *:句柄

char *path:此处可以带路径或者不带路径。类似于上述示例写的myfile.c,当他运行起来时就已经有了当前路径:CWD。如果带路径:/home/xxx/xxx.txt(此为明确路径),如果不带路径:"log.txt"(默认路径),C语言会将此拼接为CWD/"log.txt"。不过打开一个文件,它必须带路径。因为它需要在系统中找到文件,而在Linux系统中,找文件必须带路径!

const char *mode:打开文件目的是访问文件,而常用方式见下:

2.1.2fclose

关闭时将文件全部flush到它的指针fp

2.2对文件做写入

它们功能上不重叠

2.2.1fwrite

注意:在传入具体大小时不考虑\0,因为它是以文件的形式传入的,文件不关心是否有无\0

复制代码
int main()
{
    FILE *fp = fopen("log.txt", "w");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    const char *message = "hello world\n";
    int cnt = 0;
    while(cnt < 10)
    {
        fwrite(message, 1, strlen(message), fp);
        //fputs(message, fp);
        //fprintf(fp, "hello world:%d\n", cnt);
        cnt++;
    }

    fclose(fp);
    return 0;
}

2.2.2fputs

像指定的文件流中写入字符串

复制代码
int main()
{
    FILE *fp = fopen("log.txt", "w");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    const char *message = "hello world\n";
    int cnt = 0;
    while(cnt < 10)
    {
        fputs(message, fp);
        cnt++;
    }

    fclose(fp);
    return 0;
}

2.2.3fprintf

以向显示器格式化输出一样,向指定文件流做写入

复制代码
int main()
{
    FILE *fp = fopen("log.txt", "w");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    int cnt = 0;
    while(cnt < 10)
    {
        fprintf(fp, "hello world:%d\n", cnt);
        cnt++;
    }

    fclose(fp);
    return 0;
}

2.3fopen各个模式以及拓展

2.3.1w模式

现在,在log.txt中输入字符,再通过fputs向log.txt写入:

运行,结果原字符都被覆盖:

原因是因为,在C语言中,w模式在每次写入之前会将目标文件清空,并从头写入,如果目标文件不存在,它能新建:

2.3.2输出重定向

代码: >log.txt

符号 > ,表示将log.txt打开一次再关闭一次,而它打开文件时运行的就是w

2.3.3getcwd

获取当前进程的"当前工作目录"的绝对路径

复制代码
char pwd[64];
getcwd(pwd,sizeof(pwd));                               
printf("cwd: %s\n", pwd);  

2.3.4chdir

切换当前进程的工作目录。

与2.3.3相结合:

复制代码
int main()
{
    chdir("/home/xxx404/linux-learning/test2_2");
    char pwd[64];
    getcwd(pwd,sizeof(pwd));
    printf("cwd: %s\n", pwd);
    
    FILE *fp = fopen("log.txt", "w");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    fclose(fp);
    return 0;
}

此时,在相关路径下就有了log.txt文件:

结论:Linux 中用相对路径打开文件时,文件的实际位置由进程的 "当前工作目录(CWD)" 决定,而非程序本身的存放目录

2.3.5a模式

全称:append,追加,每次写从结尾写。

复制代码
    FILE *fp = fopen("log.txt", "a");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    } 
    const char *message = "hello world\n";
    fputs(message, fp);

现象:

2.3.6追加重定向

>>log.txt,其写入方式和a模式相同

2.4读

2.4.1fread

2.4.2fgets

2.4.3fscanf

2.4.4feof

用于判断文件指针是否已经超越过文件末尾。

代码:

复制代码
    FILE *fp = fopen("log.txt", "r");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    char inbuffer[1024];
    while(1)
    {
        if(!fgets(inbuffer,sizeof(inbuffer), fp))
        {
            break;
        }
        printf("file: %s", inbuffer);
    }

运行结果:

2.5对读和写的理解

当我们用cat获取文件内容时,如果是这样:

abcd

1234

那么在OS视角为:abcd\n1234

我们可以将文件的全部内容想象为一个一维字符数组char content[],而要想获取系统读写文件到哪一个位置,就需要ftell函数:

代码:

复制代码
  FILE *fp = fopen("log.txt", "r");
    if(NULL == fp)
    {
        perror("fopen");
        return 0;
    }

    char inbuffer[1024];
    while(1)
    {
        long pos = ftell(fp);
        printf("pos: %ld\n", pos);
        int ch = fgetc(fp);
        if(ch == EOF)
        {
            break;
        }
        printf("%c\n", ch);
      
        printf("file: %s", inbuffer);
    }

结果:

3.系统级别的文件操作

3.1学习系统调用

open

两个参数的open适用于文件存在,三个参数的适用于文件不存在,其中mode表示创建文件的权限。

path:同fopen,表示路径

flags:表示打开文件的模式

常见模式,这里表示形式是宏,本质是"位掩码",每个宏对应一个唯一的二进制位,

模式由int类型构成,也就是存在32个比特位,那么就可以通过比特位传递标志位完成高效的多标志传递设计。

位图传参

通过demo链接宏和比特位传递标志位

复制代码
#define ONE (1<<0)   // 1
#define TWO (1<<1)   // 1
#define THREE (1<<2) // 4
#define FOUR (1<<3)  // 8
#define FIVE (1<<4)  // 16

void Print(int flags)
{
    if(flags & ONE)
        printf("ONE\n");
    if(flags & TWO)
        printf("TWO\n");
    if(flags & THREE)
        printf("THREE\n");
    if(flags & FOUR)
        printf("FOUR\n");
    if(flags & FIVE)
        printf("FIVE\n");
}

int main()
{
    Print(ONE);
    printf("\n");
    Print(TWO);
    printf("\n");
    Print(ONE | TWO);
    printf("\n");
    Print(ONE | TWO | THREE);
    printf("\n");
    Print(ONE | TWO | THREE | FOUR);
    printf("\n");
    Print(TWO | THREE | FOUR | FIVE);
}

运行结果:

说明我们可以采用一个整数的32位比特位,用不同比特位的0,1表示是否,不同的比特位位置代表不同的选项,特定位置的0,1可以传递不同标志位,也就是说,可以通过一个整数去传递32个标志位。

所以上述模式的宏,只有一个比特位是1的整数值。要是想传递标志位时,可以将其组合传递给flags,从而得到不同效果,这就是位图传参

文件描述符fd

如果打开成功,会有文件描述符file descriptor(fd),如果打开失败,会有错误码

fd是一个大于等于0的整数,所以,我们将open前的int返回值称为文件描述符,它可以用来标识一个被打开的文件,它也是一个句柄!!!(凡是标识资源唯一性的都可以被称为句柄)

3.2代码

3.2.1产生与fopen中w模式相同的效果

标志位(多个宏按位或):

O_CREAT:如果文件不存在,则创建该文件(必须配合第三个参数指定权限);

O_WRONLY:以只写模式打开文件(无法读取);

O_TRUNC:如果文件已存在且是普通文件,清空其所有内容(长度截断为 0)

此时,系统调用的方式与fopen产生的效果相同

复制代码
int main()
{
    umask(0); // 只改变当前进程的umask
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    //const char *message = "1234567890abcdefg\n";
    const char *message = "ccc";
    write(fd, message, strlen(message));

    close(fd);
    return 0;
}

3.2.2产生与fopen中a模式相同的效果

代码:

复制代码
int main()
{
    umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    const char *msg = "hello world\n";
    int cnt = 10;
    while(cnt--)
    {
        write(fd, msg, strlen(msg));
    }

    close(fd);

    return 0;
}

本章完。

相关推荐
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][i2c]i2c-dev
linux·笔记·学习
angushine3 小时前
银河麒麟V10创建用户
运维
Trouvaille ~3 小时前
【Linux】网络编程基础(二):数据封装与网络传输流程
linux·运维·服务器·网络·c++·tcp/ip·通信
久绊A4 小时前
春节前云平台运维深度巡检-实操经验
运维·安全·容器·kubernetes·云平台
万邦科技Lafite4 小时前
一键获取京东商品评论信息,item_reviewAPI接口指南
java·服务器·数据库·开放api·淘宝开放平台·京东开放平台
旅途中的宽~4 小时前
【深度学习】通过nohup后台运行训练命令后,如何通过日志文件反向查找并终止进程?
linux·深度学习
梦想的旅途24 小时前
企业微信API外部群自动化推送:从“群发工具”到“智能触达”的架构实践
运维·自动化·企业微信
yuezhilangniao5 小时前
Next.js 项目运维手册-含-常用命令-常见场景
运维·开发语言·reactjs
dump linux5 小时前
内核驱动调试接口与使用方法入门
linux·驱动开发·嵌入式硬件