前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核编程--文件流与缓冲区

Linux内核编程--文件流与缓冲区

作者头像
Coder-Z
发布2022-05-09 21:42:21
2.8K0
发布2022-05-09 21:42:21
举报

Linux系统下,通过编程对文件进行操作的方式有两种机制:文件描述符文件流

1.文件描述符和文件流的区别:

文件描述符的类型为int,文件流的类型为FILE*(文件指针)。

文件描述符的操作更底层,文件流的操作更高级且更丰富。

文件流是基于文件描述符来实现的,所以可以从文件流中提取并操作文件描述符,比如“int fileno(FILE*); fileno(file_stream)”。

对于一个文件,如果涉及到格式化的输入/输出,以及面向字符或行的输入/输出,更推荐使用文件流进行操作。

2.文件流的定向设置

文件流的定向决定了一个I/O操作一次能操作多少个字节,是单字节(字节定向)还是多字节(宽定向)。

fwide()用于设置流的定向

代码语言:javascript
复制
#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);

--返回值:若流是宽定向,返回正数;若流的字节定向,返回负数;若流是未定向的,返回0

mode为负数,fwide将指定文件流可以进行单字节I/O操作(字节定向)
mode为正数,fwide将指定文件流可以进行多字节I/O操作(宽定向)
mode为0,不设置定向,但返回该流定向的值

*fwide不改变已定向的流

代码样例:

代码语言:javascript
复制
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
void try_read(FILE* fp)
{
    int c = fgetc(fp);
    if(c == EOF) puts("narrow character read failed");
    else printf("narrow character read '%c'\n", c);
    wint_t wc = fgetwc(fp);
    if(wc == WEOF) puts("wide character read failed");
    else printf("wide character read '%lc'\n", wc);
}
void show(int n)
{
    if(n == 0) puts("no orientation");
    else if (n < 0) puts("narrow orientation");
    else puts("wide orientation");
}
int main(void)
{
    FILE* fp = fopen("test.txt","r");
    if (!fp) {
        perror("fopen() failed");
        return EXIT_FAILURE;
    }
    // A newly opened stream has no orientation.
    show(fwide(fp, 0));
    // Establish byte orientation.
    show(fwide(fp, -1));
    try_read(fp);

    // Only freopen() can reset stream orientation.
    if (freopen("test.txt","r",fp) == NULL)
    {
       perror("freopen() failed");
       return EXIT_FAILURE;
    }
  
    // Establish wide orientation.
    show(fwide(fp, 1));
    try_read(fp);
    fclose(fp);
}

运行结果:

代码语言:javascript
复制
no orientation
narrow orientation
narrow character read 'a'
wide character read failed
wide orientation
narrow character read failed
wide character read 'a'

3.缓冲区大小设置

Linux内核操作文件会使用高速缓冲区。比如write操作, 进程把数据写到缓冲区, 然后内核把数据从缓冲区写到磁盘文件。

当进程不断写入数据时,内核可以等缓冲区满了再一次性往磁盘写入,这样可以提高性能。

流程图如下:

缓冲分三种模式:

(1)全缓冲,写满标准I/O缓冲区后才进行I/O操作, 例如磁盘文件(非交互式设备)的I/O操作

(2)行缓冲,在输入/输出中遇到换行符时才进行I/O操作,例如在终端进行I/O操作

(3)无缓冲,写入字符后立马进行读操作,例如标准错误流stderr

刷新缓冲区函数:

代码语言:javascript
复制
include <stdio.h>
int fflush( FILE *stream );

变更缓冲的函数--setbuf()/setvbuf()

代码语言:javascript
复制
void setbuf( FILE *restrict stream, char *restrict buffer);
--stream为文件流指针,buf为缓冲区首地址

若成功则返回0,若出错则为非0
代码语言:javascript
复制
int setvbuf( FILE *restrict stream, char *restrict buffer,
             int mode, size_t size );
--stream为文件流指针,buf为缓冲区首地址,mode为缓冲区类型,size为缓冲区内字节的数量
--mode参数如下:
_IOFBF:全缓冲模式
_IOLBF:行缓冲模式
_IONBF:无缓冲模式

若成功则返回0,若出错则为非0

代码样例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
int main(void)
{
    char buff[1024];
    memset(buff, '\0', sizeof(buff));
    struct stat file_stats;

    FILE* fp = fopen("test.txt", "w+");
    fstat(fileno(fp), &file_stats);
    printf("before setvbuf: file buf size is %ld\n", file_stats.st_blksize);

    if(setvbuf(fp, buff, _IOFBF, 1024) != 0) {
       perror("setvbuf failed");
       return EXIT_FAILURE;
    }

    fclose(fp);
    
    return EXIT_SUCCESS;
}

4.文件流的打开和关闭

常用的open函数--fopen()/freopen()/fdopen()

fopen():打开路径名为pathname的指定文件

freopen():在一个指定的流上打开文件。若该流已经打开,先关闭该流。若该流已设置定向类型,则先清除该定向

fdopen():打开一个已有的文件描述符。该文件描述符的来源:open、dup、dup2、fcntl、pipe、socket、socketpair、accept...

代码语言:javascript
复制
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);

--若成功,返回文件指针FILE*;若出错,返回NULL

三个函数中的type参数,有下面这些取值:

关闭一个打开的流--fclose()

代码语言:javascript
复制
#include <stdio.h>
int fclose(FILE *fp)

5.单个字符操作

从文件流中一次读一个字符

代码语言:javascript
复制
#include <stdio.h>
int getc(FILE *fp)
int fgetc(FILE *fp)
int getchar(void) /*等同于getc(stdin)*/

上面的函数在出错或到达文件末尾时,都返回同样的值,为了区分是出错还是到达文件末尾,可以调用ferror(FILE *fp)/feof(FILE *fp)。

从流中读取数据以后,可以调用ungetc(int c, FILE *fp)将字符再送回到流中。

对应于getc()的操作,在文件流中一次写一个字符

代码语言:javascript
复制
#include <stdio.h>
int putc(int c, FILE *fp)
int fputc(int c, FILE *fp)
int putchar(int c) /*等同于putc(c, stdout)*/

6.行操作

一次从流中读出一行

代码语言:javascript
复制
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp)
char *gets(char *buf)

--若成功,返回buf;若出错,返回NULL

一次往流中写入一行

代码语言:javascript
复制
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp)
int puts(const char *str)

7.二进制操作

一次读/写一个二进制数组或者结构体

代码语言:javascript
复制
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)

8.其他流操作

返回文件流中文件指针的位置:

代码语言:javascript
复制
long ftell( FILE *stream );

设置文件流中文件指针位置

代码语言:javascript
复制
int fseek( FILE * stream, long offset, int origin );

将文件流的读写位置移至文件开头

代码语言:javascript
复制
void rewind( FILE *stream );

删除指定文件

代码语言:javascript
复制
int remove( const char *fname );

格式化流

代码语言:javascript
复制
int scanf( const char *restrict format, ... );
int fscanf( FILE *restrict stream, const char *restrict format, ... );
int sscanf( const char *restrict buffer, const char *restrict format, ... );

int printf( const char *restrict format, ... );
int fprintf( FILE *restrict stream, const char *restrict format, ... );
int sprintf( char *restrict buffer, const char *restrict format, ... );

代码样例:

Demo1:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const char* fname = "test.txt";
    FILE* fp = fopen(fname, "w+");
    if(!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    fputs("Hello, world!\n", fp);
    rewind(fp);
    int c; // note: int, not char, required to handle EOF
    while ((c = fgetc(fp)) != EOF) {
       putchar(c);
    }
    if (ferror(fp)) {
        puts("I/O error when reading");
    } else if (feof(fp)) {
        puts("End of file reached successfully");
    }
    fclose(fp);
    remove(fname);
    return EXIT_SUCCESS;
}

运行结果:

代码语言:javascript
复制
Hello, world!
End of file reached successfully

Demo2:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    #define SIZE 5
    double A[SIZE] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double B[SIZE];

    FILE * fp = fopen("test.bin", "wb");
    fwrite(A, sizeof(double), SIZE, fp);
    fclose (fp);
    
    fp = fopen("test.bin", "rb");
    fseek(fp, sizeof(double) * 3L, SEEK_SET);
    fread(B, sizeof(double), 1, fp);    
    printf("file point position is : %ld\n", ftell(fp));
    printf("B[0] == %.1f\n", B[0]);                

    memset(B, 0.0, sizeof(B));

    fseek(fp, sizeof(double) * 4L, SEEK_SET);
    fread(B, sizeof(double), 1, fp);   
    printf("file point position is : %ld\n", ftell(fp));
    printf("B[0] == %.1f\n", B[0]);   

    fclose(fp);

    return EXIT_SUCCESS;
}

运行结果:

代码语言:javascript
复制
file point position is : 32
B[0] == 4.0
file point position is : 40
B[0] == 5.0

参考教程:

《UNIX环境高级编程第3版》

https://en.cppreference.com/w/c/io/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档