【Linux】文件操作

目录

一、知识铺垫

二、回顾一下之前C语言的文件操作,并对比重定向

1、w选项与输出重定向(>)

2、a选项与追加重定向(>>)

3、熟悉一下读写操作

4、练习读操作:

三、什么叫做当前路径?当前路径与文件创建的关系?

四、访问文件的系统调用

1、程序默认打开的文件流

​编辑

2、常见的读写函数:

3、open系统调用:

4、一个小细节:用位图传参:

5、open参数解析:

6、写文件的系统调用:write

7、关闭文件的系统调用:close

五、文件描述符(fd)

1、认识文件描述符

2、文件描述符具体是什么?为什么后续访问文件的系统调用都要通过fd来操作?

3、一切皆文件

4、文件描述符表的分配规则以及利用规则实现重定向

(1)文件描述符表的分配规则

(2)改变重定向的系统调用(dup2)

(3)dup2使用场景

5、给自定义shell增加重定向功能

六、缓冲区问题:

1、简单介绍

2、为什么使用缓冲区能提高效率?

3、缓冲区在哪里?

4、用代码证明缓冲区的存在

七、模拟实现文件操作的常用接口(有缓冲区和无缓冲区版本)

1、无缓冲区版本

mystdio.h

mystdio.c

filetest.c

2、有缓冲区版本

mystdio.c

mystdio.h

filetest.c


前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家

点击跳转到网站

一、知识铺垫

(1)文件 = 内容 + 属性

(2)访问文件之前,都得先打开该文件。修改文件都是通过执行代码的方式完成修改。

(3)打开文件前提要文件必须加载到内存中

(4)由谁打开文件?进程打开文件。

(5)一个进程可以打开多少文件?可以打开多个文件

(6)一定时间内,系统中会存在多个进程,也可能同时存在更多的被打开文件,OS要不要管理多个被进程打开的文件呐?答案是肯定要管理的。如何管理呐?答案是先描述在组织。

(所以内核中一定要有描述被打开文件的结构体,并用其定义对象)。

(7)进程和文件的关系:结构体之间的关系,struct task_struct 和 struct XXX

(8)系统中是不是所有的文件都被进程打开了?答案是:并不是,那些没有被打开的文件是被存储磁盘中的,所以也叫磁盘文件。

二、回顾一下之前C语言的文件操作,并对比重定向

cpp 复制代码
#include<stdio.h>
int main()
{
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* str = "hello file\n";
    fputs(str,fp);
    fclose(fp);
    return 0;
}

1、w选项与输出重定向(>)

以" w "选项打开文件,是对文件进行写操作,但是打开前会将文件原有内容清空。而重定向(" > ")也是会将文件原有内容清空,因为重定向之前需要将文件打开,而打开这个操作就会将文件内容清空:

2、a选项与追加重定向(>>)

fopen以"a"选项打开文件,是追加的方式进行写,即在文件原有内容的末尾接着写,这与追加重定向功能一样:

cpp 复制代码
#include<stdio.h>
int main()
{
    FILE *fp = fopen("./log.txt","a");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* str = "hello file\n";
    fputs(str,fp);
    fclose(fp);
    return 0;
}

还有其他选项可以查手册。

3、熟悉一下读写操作

cpp 复制代码
#include<stdio.h>
#include<string.h>

#define FILENAME "log.txt"

//练习读写操作
int main()
{
    FILE* fp = fopen(FILENAME,"w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello HF";
    int cnt = 6;
    while(cnt)
    {
        int n = fwrite(msg,strlen(msg),1,fp);
        printf("write %d block\n",n);
        cnt--;
    }

    fclose(fp);
    return 0;
}

4、练习读操作:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define FILENAME "log.txt"

//练习读
int main()
{
    FILE* fp = fopen(FILENAME,"r");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while(1)
    {
        char* r = fgets(buffer,sizeof(buffer),fp);
        if(!r) break;//返回NULL则终止读
        printf("%s\n",buffer);
    }

    return 0;
}

三、什么叫做当前路径?当前路径与文件创建的关系?

当前路径指进程启动时所在的工作目录。进程启动时,会自动记录自己启动时的所在的目录,可通过指令查看:(ls /proc/进程pid -l)


当前路径与文件创建的关系:以前都以为文件是默认创建在可执行程序的同级目录,实则不然,文件是默认创建在进程的工作目录下。

如果我们在创建文件之前,修改进程的工作目录,那么文件也会创建到修改后的工作目录下:

修改进程的工作目录的接口:

参数就是要修改的工作目录。

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define FILENAME "HF.txt"

//修改进程的工作目录
int main()
{
    int i = chdir("/root/study/linux-learning");
    if(i)
    {
        printf("转换失败\n");
    }
    FILE* fp = fopen(FILENAME,"w");
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello HF";
    int cnt = 6;
    while(cnt)
    {
        int n = fwrite(msg,strlen(msg),1,fp);
        printf("write %d block\n",n);
        cnt--;
    }

    fclose(fp);
    return 0;
}

四、访问文件的系统调用

1、程序默认打开的文件流

2、常见的读写函数:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define FILENAME "log.txt"

//常见的写函数
int main()
{
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");//将数据输出到标准输出中(stdout显示器设备);
    fputs("hello fputs\n",stdout);//也是将数据输出到标准输出中,但不能像fprintf那样支持格式化输出
    const char* msg = "hello fwrite\n";
    fwrite(msg,1,strlen(msg),stdout);
    return 0;
}
cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define FILENAME "log.txt"

int main()
{
    //fscanf
    char buffer[64];
    fscanf(stdin,"%s",buffer);//从标准输出(键盘)中读取数据放到buffer中,空格和换行符作为分隔符
    printf("%s\n",buffer);
    return 0;
}

3、open系统调用:

访问文件不仅仅有C语言的文件接口,OS还必须提供对应的访问文件的系统调用,就是open系列的系统调用:

4、一个小细节:用位图传参:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

#define FILENAME "log.txt"

//用位图传参
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)

void myPrint(int flag)
{
    if(flag & ONE) printf("1");
    if(flag & TWO) printf("2");
    if(flag & THREE) printf("3");
    if(flag & FOUR) printf("4");
    if(flag & FIVE) printf("5");
    printf("\n");
}

int main()
{
    myPrint(ONE);
    myPrint(TWO);
    myPrint(ONE | TWO);
    myPrint(THREE | FOUR | FIVE);
    myPrint(FIVE);
    return 0;
}

5、open参数解析:

(1)参数一:pathname,是要打开或者创建的文件路径名(绝对路径/相对路径)

(2)参数二:flags,标志位,表示打开文件的方式,具体值是宏定义(可查看手册),传参方式类似于第4点的位图传参方式,可以通过按位或(|)设置多个标志。

注意:使用这些标志位需要包含头文件:<fcntl.h>

三个基本标志位:

标志 作用
O_RDONLY 只能读取,不能写入,若进行写操作会返回 EBADF 错误
O_WRONLY 只能写入,不能读取,需配合 O_CREAT 创建新文件
O_RDWR 可同时读取和写入,写入可能覆盖原有内容,需控制偏移量

其他标志如下:

标志 作用
O_CREAT 如果文件不存在,则创建它(需配合第三个参数 mode 使用)。
O_EXCL O_CREAT 联用,若文件已存在则返回错误(可用于避免文件被意外覆盖)。
O_TRUNC 若文件存在且为可写模式,将其长度截断为 0(即打开文件前先清空文件内容)。
O_APPEND 追加写,写入时始终追加到文件末尾(自动将文件偏移量设置到文件末尾)。
O_NONBLOCK 以非阻塞模式打开文件(用于 I/O 多路复用,如网络编程)。

(3)参数三:mode,用于指定新建文件的权限,仅当使用 O_CREATO_TMPFILE 标志时生效。有时候,如果不指定mode参数,那么创建的文件的权限可能会乱码:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h> 

#define FILENAME "log.txt"

//使用open
int main()
{
    int fd = open("HF.txt",O_WRONLY | O_CREAT);
    if(fd == -1)
    {
        perror("open");
        return 1;
    }
    return 0;
}

如图会发现创建的文件的权限位乱码的,所以此时我们需要使用第三个参数code解决:

但受系统权限掩码的限制,会导致创建的文件的权限与我们设置的权限不一样,此时需要提前使用一个系统调用:umask(0),头文件: <sys/stat.h>

解释:用于设置当前进程的文件创建掩码(file creation mask)为 0。文件创建掩码是一个位掩码,用于在创建文件或目录时屏蔽某些权限位,从而控制新创建文件的默认权限。

此时就与我们设置的权限一样了。

6、写文件的系统调用:write

参数解析:

参数名称 数据类型 描述
fd int 文件描述符 ,指向已打开的文件、管道、套接字或设备(如标准输入stdin对应 fd = 0;标准输出stdout对应 fd = 1;标准错误stderr对应 fd = 2)。 通过open系统调用获取,用于标识写入目标。
buf const void * 写入缓冲区指针,它是一个指向用户空间缓冲区的指针,这个缓冲区里存储着准备写入的数据。数据的传输方向是从 用户空间(buf)到内核空间(fd 对应的设备或文件)。 可以是字符数组、结构体或其他数据类型,需确保内存访问权限合法。
count size_t 要写入的字节数 ,指定从buf中读取的最大数据量。 实际写入字节数可能小于count(如遇到文件末尾、磁盘空间不足或权限限制)。
cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h> 
#include<sys/stat.h> 
#define FILENAME "log.txt"


int main()
{
    umask(0);
    int fd = open("HF.txt",O_WRONLY | O_CREAT,0666);
    const char* str = "hello write\n";
    write(fd,str,strlen(str));
    return 0;
}

注意:

7、关闭文件的系统调用:close

五、文件描述符(fd)

1、认识文件描述符

这是一个及其重要的概念,文件描述符也就是open函数的返回值,我们先看看值是什么?

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h> 
#include<sys/stat.h> 
#define FILENAME "log.txt"

//认识文件描述符
int main()
{
    int fd1 = open("HF1.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd2 = open("HF2.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd3 = open("HF3.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd4 = open("HF4.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd5 = open("HF5.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);

    printf("fd1:%d\n",fd1);
    printf("fd2:%d\n",fd2);
    printf("fd3:%d\n",fd3);
    printf("fd4:%d\n",fd4);
    printf("fd5:%d\n",fd5);
    return 0;
}

可以看到值其实就是整形,但为什么是从3开始呐?

因为0,1,2端口已经默认被三个标准占用了:

标准输入(stdin):0

标准输出(stdout):1

标准错误(stderr):2


由上述知识我们可以知道,C语言的相关文件接口,本质就是封装了各个系统调用,主要是为了保证自己的跨平台性。

2、文件描述符具体是什么?为什么后续访问文件的系统调用都要通过fd来操作?

上面我们知道,进程要管理打开的文件需要先描述后组织。

(1)首先task_struct中存在一个成员变量(struct file_struct *files),这个成员指向的结构体(struct file_struct)里面存在一个成员,该成员表示进程打开的文件描述符表。

(2)所谓文件描述符表也就是一个数组,其数组类型为(struct file** fd_array[ ]),这是一个二级指针,其内容的类型为(struct file*)

(3)struct file是文件结构体,里面包含了文件属性、方法集、文件运行时的状态信息、操作函数和资源引用等等信息(如下图),被称为文件操作的 "控制器"。

(4)而open返回值fd(文件描述符)就是这个文件描述符表的下标,有了这个下标,我们就可以找到下标对应的struct file,从而就可以操作这个文件,所以后续访问文件的系统调用都要通过fd来操作的。

3、一切皆文件

4、文件描述符表的分配规则以及利用规则实现重定向

(1)文件描述符表的分配规则

文件描述符表的分配规则:会叫最小的没有被使用的下标,分配给最新打开的文件。

输出重定向的现象:

(2)改变重定向的系统调用(dup2)

我们先学习dup2系统调用,参数解析如下:

(1)oldfd:已存在的、有效的文件描述符,指向一个已打开的文件、设备或套接字。

|-----------------------------------------------------------|
| 若 oldfd 无效(如未打开或已关闭),dup2() 返回 -1 并设置 errno=EBADF |

(2)newfd:新绑定的文件描述符,dup2() 会将文件对象以前的文件描述符oldfd解绑,然后newfd进行绑定

|---------------------------------------------------------------------------------------|
| newfd 未打开 : 直接将 newfd 指向 oldfd 对应的 struct file 对象。 |
| newfd 已打开 : 先关闭 newfd(减少其原 struct file 的引用计数),再复制 oldfd 的文件对象到 newfd。 |
| newfd == oldfd : 不执行任何操作,直接返回 newfd(避免自我关闭)。 |

(3)dup2使用场景

重定向到文件:

cpp 复制代码
//使用dup2
int main()
{
    int fd = open("newfile",O_WRONLY|O_CREAT|O_TRUNC,0666);
    dup2(fd,1);//将newfile文件与标准输出进行绑定
    //这样printf就会默认向上述文件进行输出
    printf("hello newfile\n");
    return 0;
}

从文件读取内容到数组:

cpp 复制代码
int main()
{
    int fd = open("newfile",O_RDONLY,0666);

    dup2(fd,0);//将文件的文件描述符与标准输出进行绑定
    char buffer[1024];
    while(1)
    {
        //默认情况,stdin会从键盘中读取,若键盘不输入,是会发生阻塞的
        char* s = fgets(buffer,sizeof(buffer),stdin);//此时stdin会默认从文件中读取
        if(s==NULL)break;
        printf("file content:%s",buffer);
    }
    return 0;
}

5、给自定义shell增加重定向功能

六、缓冲区问题:

1、简单介绍

缓冲区其实是一块内存区域,目的是用来提高使用者的效率(空间换时间)。

比如从云南到北京运送货物,总共需要运送100kg,如果一次运送10kg,需要来回10次,这样花费的时间就非常多;如果我用比较大的运输机一次就能运送100kg,这样就用运送一次,大大提高了效率。

2、为什么使用缓冲区能提高效率?

注意:平时我们所说的,包括这里即将减少的缓冲区都是语言层面的缓冲区(比如C语言里面的缓冲区),与OS内核中的缓冲区没有关系。

为什么使用语言层面的缓冲区能提高效率?

结合上述运送物资的例子,我们知道通过系统调用访问OS是需要有很大开销的,如果我们语言层面不设置缓冲区,那么来一点数据就送给OS,又来一点数据又会访问OS,这样就会多出很多开销,但如果我们在语言层设置一个缓冲区,让需要存储的数据线一点一点累积保存到缓冲区,达到一定的空间后,我们一次性传输给OS,这样访问OS的次数就大大减少了,从而就提高了效率。

3、缓冲区在哪里?

缓冲区是在FILE结构体中,也就是由FILE结构体来维护缓冲区:

缓冲区常见字段:

cpp 复制代码
struct _IO_FILE {
    // 基础文件描述符
    int _fileno;
    
    // 缓冲区指针与状态
    char* _IO_read_ptr;      // 读缓冲区当前位置
    char* _IO_read_end;      // 读缓冲区结束位置
    char* _IO_read_base;     // 读缓冲区起始位置
    
    char* _IO_write_base;    // 写缓冲区起始位置
    char* _IO_write_ptr;     // 写缓冲区当前位置
    char* _IO_write_end;     // 写缓冲区结束位置
    char* _IO_buf_base;      // 缓冲区基址
    char* _IO_buf_end;       // 缓冲区结束地址
    
    // 缓冲区状态标志
    int _IO_write_base;      // 写缓冲区起始位置(重复字段,实际为标志位)
    unsigned _flags;         // 缓冲区标志(如是否全缓冲、行缓冲等)
    unsigned _IO_file_flags; // 文件状态标志
    
    // 缓冲区大小与类型
    int _IO_buf_size;        // 缓冲区大小
    int _mode;               // 读写模式
    
    // ... 其他字段(省略)
};

核心字段:

4、用代码证明缓冲区的存在

cpp 复制代码
//证明缓冲区的存在
int main()
{
    //使用系统调用
    const char* s1 = "hello write\n";
    write(1,s1,strlen(s1));
    //使用C语言接口
    const char* s2 = "hello fprintf\n";
    fprintf(stdout,"%s",s2);

    const char* s3 = "hello fwrite\n";
    fwrite(s3,strlen(s3),1,stdout);
    fork();
    return 0;
}

如果我们直接运行那么就会正常打印,因为显示器是行刷新(即写完一行就刷新数据(\n))

但如果我们重定向到某个文件,会发现一个奇怪的现象:

C语言接口的内容会存在两份,而系统调用的接口内容只有一份

因为我们使用了重定向,重定向的刷新策略是全缓存刷新(即缓冲区满了才刷新数据),但很显然代码中的两条内容是塞不满缓冲区的,所以此时会一直等待,最后会遇到fork创建子进程,而刷新数据也属于修改数据的一种方式,父子进程中任意一个进程修改共享数据时,都会进行写实拷贝,最后进程结束,缓冲区强迫刷新,父子进程都会向文件中刷新数据,所以C语言接口的数据会存在两份,而系统调用接口write会直接将内容存在系统内部的缓冲区,此时内容与父子进程无关,所以只有一份数据。

其次printf、scanf等等函数的格式化输出也与缓冲区有关,可以搜索了解了解。

七、模拟实现文件操作的常用接口(有缓冲区和无缓冲区版本)

1、无缓冲区版本

mystdio.h

cpp 复制代码
#pragma once
#include<stdio.h>

typedef struct _myFILE
{
    int fileno;
}myFILE;

myFILE* my_fopen(const char* pathname,const char* mode);
int my_fwrite(myFILE* fp,const char* fs,int size);
//int my_fread();
void my_fclose(myFILE* fp);

mystdio.c

cpp 复制代码
#include "mystdio.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

myFILE *my_fopen(const char *pathname, const char *mode)
{
    int flag = 0;
    if (strcmp(mode, "r") == 0)
    {
        flag |= O_RDONLY;
    }
    else if (strcmp(mode, "w") == 0)
    {
        flag |= (O_CREAT | O_WRONLY | O_TRUNC);
    }
    else if (strcmp(mode, "a") == 0)
    {
        flag |= (O_CREAT | O_WRONLY | O_APPEND);
    }
    else
    {
        return NULL;
    }
    int fd = 0;
    if (flag & O_WRONLY)
    {
        umask(0);
        fd = open(pathname, flag, 0666);
    }
    else
    {
        fd = open(pathname, flag);
    }
    if (fd < 0)
        return NULL;
    myFILE *fp = (myFILE *)malloc(sizeof(myFILE));
    if (fp == NULL)
        return NULL;
    fp->fileno = fd;
    return fp;
}

int my_fwrite(myFILE* fp,const char* s,int size)
{
    return write(fp->fileno,s,size);
}

void my_fclose(myFILE* fp)
{
    close(fp->fileno);
    free(fp);
}

filetest.c

cpp 复制代码
#include"mystdio.h"
#include<string.h>

const char* filename = "./log.txt";

int main()
{
    myFILE* fp = my_fopen(filename,"w");
    if(fp == NULL) return 1;

    const char* s = "hello myflie\n";
    my_fwrite(fp,s,strlen(s));

    my_fclose(fp);
    return 0;
}

2、有缓冲区版本

mystdio.c

cpp 复制代码
#include "mystdio.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>


myFILE *my_fopen(const char *pathname, const char *mode)
{
    int flag = 0;
    if (strcmp(mode, "r") == 0)
    {
        flag |= O_RDONLY;
    }
    else if (strcmp(mode, "w") == 0)
    {
        flag |= (O_CREAT | O_WRONLY | O_TRUNC);
    }
    else if (strcmp(mode, "a") == 0)
    {
        flag |= (O_CREAT | O_WRONLY | O_APPEND);
    }
    else
    {
        return NULL;
    }
    int fd = 0;
    if (flag & O_WRONLY)
    {
        umask(0);
        fd = open(pathname, flag, 0666);
    }
    else
    {
        fd = open(pathname, flag);
    }
    if (fd < 0)
        return NULL;
    myFILE *fp = (myFILE *)malloc(sizeof(myFILE));
    if (fp == NULL)
        return NULL;
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->pos = 0;
    fp->flush_mode = LINE_FLUSH;
    return fp;
}

void my_fflush(myFILE* fp)
{
    if(fp->pos == 0) return;
    write(fp->fileno,fp->outbuffer,fp->pos);
    fp->pos = 0;
}


int my_fwrite(myFILE* fp,const char* s,int size)
{
    //向缓冲区写入
    memcpy(fp->outbuffer+fp->pos,s,size);
    fp->pos += size;
    if((fp->flush_mode & LINE_FLUSH) && fp->outbuffer[fp->pos-1] == '\n')
    {
        my_fflush(fp);
    }
    else if((fp->flush_mode & LINE_FLUSH)&&fp->pos == fp->cap)
    {
        my_fflush(fp);
    }
    return size;
    //return write(fp->fileno,s,size);
}

const char* toString(int flag)
{
    if(flag & NONE_FLUSH) return "None";
    else if(flag & LINE_FLUSH)return "Line";
    else if(flag & FULL_FLUSH)return "FULL";
    return "err";
}

void DebugPrint(myFILE* fp)
{
    printf("outbuffer:%s\n",fp->outbuffer);
    printf("fd:%d\npos:%d\ncap:%d\nflush_node:%s\n",fp->fileno,fp->pos,fp->cap,toString(fp->flush_mode));
}

void my_fclose(myFILE* fp)
{
    my_fflush(fp);
    close(fp->fileno);
    free(fp);
}

mystdio.h

cpp 复制代码
#pragma once
#include<stdio.h>

#define SIZE 4096//缓冲区大小
#define NONE_FLUSH (1<<1)//无自动刷新
#define LINE_FLUSH (1<<2)//行刷新
#define FULL_FLUSH (1<<3)//全刷新


typedef struct _myFILE
{
    //char inbuffer[];
    char outbuffer[SIZE];
    int pos;
    int cap;
    int flush_mode;
    int fileno;
}myFILE;

myFILE* my_fopen(const char* pathname,const char* mode);
int my_fwrite(myFILE* fp,const char* fs,int size);
//int my_fread();
void my_fflush(myFILE* fp);
void DebugPrint(myFILE* fp);
void my_fclose(myFILE* fp);

filetest.c

cpp 复制代码
#include "mystdio.h"
#include <string.h>
#include <unistd.h>

const char *filename = "./log.txt";

int main()
{
    myFILE *fp = my_fopen(filename, "w");
    if (fp == NULL)
        return 1;

    int cnt = 5;
    char buffer[64];
    while (cnt)
    {
        snprintf(buffer, sizeof(buffer), "helloworld,hellobit,cnt:%d", cnt--);
        my_fwrite(fp, buffer, strlen(buffer));
        DebugPrint(fp);
        sleep(2);
    }

    my_fclose(fp);
    return 0;
}
相关推荐
gsls2008081 分钟前
ocrapi服务docker镜像使用
运维·docker·容器
多多*8 分钟前
微服务网关SpringCloudGateway+SaToken鉴权
linux·开发语言·redis·python·sql·log4j·bootstrap
文牧之12 分钟前
PostgreSQL 的扩展pg_freespacemap
运维·数据库·postgresql
AWS官方合作商39 分钟前
基于AWS Serverless架构:零运维构建自动化SEO内容生成系统
运维·serverless·aws
whp4041 小时前
windows server2019 不成功的部署docker经历
运维·docker·容器
IT界小黑的对象2 小时前
virtualBox部署ubuntu22.04虚拟机 NAT+host only 宿主机ping不通虚拟机
linux·运维·服务器
SilentCodeY2 小时前
Ubuntu 系统通过防火墙管控 Docker 容器
linux·安全·ubuntu·系统防火墙
weixin_527550402 小时前
Linux 环境下高效视频切帧的实用指南
linux·运维·音视频
keson要进步2 小时前
CICD实战(一) -----Jenkins的下载与安装
运维·ci/cd·centos·自动化·jenkins
keson要进步2 小时前
CICD实战(二)-----gitlab的安装与配置
linux·运维·gitlab