Introdução ao Linux Embedded 3-File IO

descritor de arquivo

Cada arquivo Linux aberto possui um descritor de arquivo correspondente e o descritor de arquivo é um número inteiro não negativo. Podemos obter um descritor de arquivo chamando a função open().

Quando o shell inicia um processo, o processo herdará três descritores de arquivo por padrão, chamados de descritores de arquivo padrão, como segue:

descritor de arquivo usar nome POSIX fluxo de estúdio
0 entrada padrão STDIN_FILENO stdin
1 saída padrão STDOUT_FILENO stdout
2 erro padrão STDERR_FILENO stderr

Ao se referir a esses descritores de arquivo no programa, você pode usar os números diretamente ou pode usar os nomes POSIX definidos em <unistd.h> **(recomendado)**. No entanto, os descritores de arquivo correspondentes ao fluxo stdio não são estáticos, são apenas seus valores iniciais, chamar a função freopen() pode alterar o valor do descritor de arquivo correspondente ao fluxo stdio, consulte as seguintes referências:

Embora as variáveis ​​stdin, stdout e stderr inicialmente se refiram à entrada, saída e erro padrão do processo, thry pode ser alterado para se referir a qualquer arquivo usando a função de biblioteca freopen() . Como parte de sua operação, freopen() pode alterar o descritor de arquivo subjacente ao fluxo reaberto. Em outras palavras, após um freopen() em stdout, por exemplo, não é mais seguro assumir que o dexcriptor de arquivo subjacente ainda é 1.

Funções de E/S de arquivo

As quatro principais funções de chamada do sistema para executar a E/S de arquivo são as seguintes (linguagens e pacotes de software geralmente usam funções de E/S encapsuladas secundárias para chamá-las indiretamente, como funções de operação de arquivo na biblioteca C: fopen(), fread ( ), fwrite(), fclose()):

int open(const char *pathname, int flags, mode_t mode);
// 打开pathname所指定的文件,并返回文件描述符,后续的函数使用文件描述符指代打开的文件,可以通过设置掩
// 码参数flags的方式在文件不存在时创建之, mode参数用于指定创建文件时文件的访问权限。
ssize_t read(int fd void *buf, size_t count);
// 从fd文件描述符所指代的文件中读取最多count字节的数据到缓冲buf,函数返回实际读取的字节数,如果读到文
// 件末尾,则返回0。
ssize_t write(int fd, const void *buf, size_t count);
// 从buf缓冲中读取count字节的数据并写入fd文件描述符所指代的文件中,函数返回实际写入文件的字节数。
int close(int fd);
//关闭文件描述符fd,此函数会释放文件描述符fd以及与之相关的内核资源。

A seguir está o código-fonte de um comando cp simples, que demonstra a aplicação prática das quatro funções acima.

/*
 * file : main.c
 * 
 */

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

#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
#define BUF_SIZE 1024
#endif

int main(int argc, char *argv[])
{
    
    
    int inputFd, outputFd, openFlags;
    mode_t filePerms;
    ssize_t numRead;
    char buf[BUF_SIZE];
    
    if (argc != 3 || strcmp(argv[1], "--help") == 0)
    {
    
    
        printf("[usage]: %s old-file now-file\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    /* open input and output files */
    
    inputFd = open(argv[1], O_RDONLY);
    if (inputFd == -1)
    {
    
    
        printf("[error]: opening file %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    
    openFlags = O_CREAT | O_WRONLY | O_TRUNC; /* O_TRUNC clear file content */
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
        		S_IROTH | S_IWOTH; /* rw-rw-rw */
    outputFd = open(argv[2], openFlags, filePerms);
    if (outputFd == -1)
    {
    
    
        printf("[error]: opening file %s\n", argv[2]);
        exit(EXIT_FAILURE);
    }
    
    /* transfer data until we encounter end of input or an error */
    
    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
    {
    
    
        if (write(outputFd, buf, numRead) != numRead)
        {
    
    
            printf("[error]: couldn't write whole buffer\n");
        	exit(EXIT_FAILURE);
        }
    }
    
    if (numRead == -1)
    {
    
    
        printf("[error]: read\n");
        exit(EXIT_FAILURE);
    }
    
    if (close(inputFd) == -1)
    {
    
    
        printf("[error]: close input\n");
        exit(EXIT_FAILURE);
    }
    
    if (close(outputFd) == -1)
    {
    
    
        printf("[error]: close output\n");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}

E/S de uso geral

Uma das características notáveis ​​do modelo UNIX I/O é seu conceito universal de entrada/saída, que é o que costumamos dizer que é um arquivo, e a função de chamada de I/O do sistema mostrada acima pode ser usada para executar operações de I/O em qualquer arquivo, por exemplo, podemos usar o exemplo de código acima assim:

// 假设代码编译成可执行文件cp1,并且cp1位于当前目录下
./cp1 /dev/tty 1.txt // 从终端读取输入并写入文件1.txt
./cp1 1.txt /dev/tty // 将文件1.txt的内容打印到终端

Outras funções comuns

lseek()

off_t lseek(int fd, off_t offset, int whence);
// 将fd文件描述符的读写指针从whence所描述的位置为起点,移动offset个字节。
// whence可以为SEEK_SET(文件开头),SEEK_CUR(当前位置),SEEK_END(文件结尾后的第一个字节)
// 函数返回调整后文件读写指针所指位置相对文件开头的偏移值

//常用使用技巧

lseek(fd, 0, SEEK_SET);   		/* Start of file */
lseek(fd, 0, SEEK_END); 		/* Next byte after the end of the file */
lseek(fd, -1, SEEK_END); 		/* Last byte of file */
lseek(fd, -10, SEEK_CUR); 		/* Ten bytes prior to current location */
lseek(fd, 10000, SEEK_END);		/* 10001 bytes past last byte of file */

imagem-20220320211322226

Esta função é usada para alterar o deslocamento do ponteiro de leitura e gravação do arquivo no descritor de arquivo. Para cada arquivo aberto, o kernel do sistema registra seu deslocamento de arquivo como a posição inicial do arquivo para a próxima operação write() ou read().

lseek não funciona com todos os tipos de arquivos e lseek() não funciona com pipes, FIFOs, soquetes ou terminais. Quando usado com o arquivo acima, a chamada falha e define a variável global errno como ESPIPE.

Se o deslocamento do arquivo do programa exceder o final do arquivo e, em seguida, executar operações de E/S, a função read() retornará 0 para indicar o final do arquivo e a função write() poderá gravar dados em qualquer posição após o final do arquivo, ** O espaço do final do arquivo até os dados recém-gravados é chamado de orifício de arquivo. **Para o programa, este espaço é um conteúdo de arquivo preenchido com 0, mas para o disco, este espaço não ocupa nenhum espaço em disco (portanto, haverá casos em que o tamanho do arquivo é maior que o espaço em disco ocupado).

O seguinte é um exemplo de programa que demonstra o uso de lseek em conjunto com read e write. As rotinas no "UNIX System Programming Manual" usam muitas funções de biblioteca escritas por mim. Eu simplesmente as substituí para que possam ser executadas diretamente. Não precisa baixar o código fonte, claro que não é tão "robusto". . .

/*
 * file : lseek_io.c
 */

// 该程序的第一个命令行参数为要打开的文件名称
// 余下的参数则指定了在文件上执行的输入/输出操作。每个表示操作的参数都以一个字母开头,
// 紧跟操作相关的值(中间不需要空格分割)。
// s<offset> : 从文件开始检索到offset字节位置
// r<length> : 在当前文件偏移量处读取length字节内容并以文本形式显示
// R<length> : 在当前文件偏移量处读取length字节内容并以十六进制形式显示
// w<str>    : 在当前文件偏移量处向文件写入str指定的字符串

#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

void usageErr(const char *format, ...)
{
    
    
    va_list argList;
    fflush(stdout);
    fprintf(stderr, "Usage: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);
    exit(EXIT_FAILURE);
}

void errExit(const char *format, ...)
{
    
    
    va_list argList;
    fflush(stdout);
    fprintf(stderr, "Error: ");
    va_start(argList, format);
    vfprintf(stderr, format, argList);
    va_end(argList);

    fflush(stderr);
    exit(EXIT_FAILURE);
}

size_t getLong(char *str)
{
    
    
    long unsigned num;
    if (sscanf(str, "%lu", &num) > 0)
    {
    
    
        return (size_t)num;
    }
    else
    {
    
    
        return 0;
    }
}

int main(int argc, char *argv[])
{
    
    
    size_t len;
    off_t offset;
    int fd, ap, j;
    char *buf;
    ssize_t numRead, numWritten;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
    {
    
    
        usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n", argv[0]);
    }

    fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); /* rw-rw-rw- */
    if (fd == -1)
    {
    
    
        errExit("open");
    }

    for (ap = 2; ap < argc; ap++)
    {
    
    
        switch (argv[ap][0])
        {
    
    
        case 'r': /* Display bytes at current offset, as text */
        case 'R': /* Display bytes at current offset, as hex */
            len = getLong(&argv[ap][1]);
            buf = malloc(len);
            if (buf == NULL)
            {
    
    
                errExit("malloc");
            }

            numRead = read(fd, buf, len);
            if (numRead == -1)
            {
    
    
                errExit("read");
            }

            if (numRead == 0)
            {
    
    
                printf("%s: end-of-file\n", argv[ap]);
            }
            else
            {
    
    
                printf("%s :", argv[ap]);
                for (j = 0; j < numRead; j++)
                {
    
    
                    if (argv[ap][0] == 'r')
                    {
    
    
                        printf("%c", isprint((unsigned char)buf[j]) ? buf[j] : '?');
                    }
                    else
                    {
    
    
                        printf("%02x ", (unsigned int)buf[j]);
                    }
                }
                printf("\n");
            }

            free(buf);
            break;

        case 'w': /* write string at current offset */
            numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
            if (numWritten == -1)
            {
    
    
                errExit("write");
            }
            printf("%s : wrote %ld bytes\n", argv[ap], (long) numWritten);
            break;

        case 's': /* Change file offset */
            offset = getLong(&argv[ap][1]);
            if (lseek(fd, offset, SEEK_SET) == -1)
            {
    
    
                errExit("lseek");
            }
            printf("%s: seek succeeded\n", argv[ap]);
            break;

        default:
            fprintf(stderr, "Argument must start with [rRws]: %s\n", argv[ap]);
        }
    }

    exit(EXIT_SUCCESS);
}

O seguinte processo de comando shell demonstra o efeito de execução do programa de exemplo acima e o fenômeno de arquivo vazio.

water@LAPTOP-Q3TGG09O:~/lseek_io$ touch file
water@LAPTOP-Q3TGG09O:~/lseek_io$ ./lseek_io file s100000 wabc
s100000: seek succeeded
wabc : wrote 3 bytes
water@LAPTOP-Q3TGG09O:~/lseek_io$ ls -l file
-rw-r--r-- 1 water water 100003 Mar 20 22:48 file
water@LAPTOP-Q3TGG09O:~/lseek_io$ ./lseek_io file s10000 R5
s10000: seek succeeded
R5 :00 00 00 00 00

ioctl()

int ioctl(int fd, unsigned long request, ...);
// 用于执行通用I/O模型之外的文件操作,比如底层设备需要进行一些通过标准I/O函数无法进行的特殊的配置,
// 则可以通过这个接口进行
// fd为文件描述符,request为特定的操作请求,后面会跟一个或多个操作相关的参数,也可能没有

O argumento fd é um descritor de arquivo aberto para o dispositivo ou arquivo no qual a operação de controle especificada pela solicitação deve ser executada. Os arquivos de cabeçalho específicos do dispositivo definem constantes que podem ser passadas no argumento de solicitação. Conforme indicado pela notação C reticências (…) padrão, o terceiro argumento para ioctl(), que rotulamos de argp, pode ser de qualquer tipo. O valor do argumento request permite que ioctl() determine que tipo de valor esperar em argp. Normalmente, argp é um
ponteiro para um inteiro ou uma estrutura; em alguns casos, não é utilizado.

Resumir

Para operações de arquivo comuns, geralmente usamos primeiro a função open() para abrir o arquivo e obter o descritor de arquivo, depois usamos a função read()/write()/lseek() para executar operações relacionadas e, finalmente, usamos close() para fechar o arquivo.

Para todas as operações de dispositivo e arquivo não incorporadas ao modelo geral de E/S, use a função ioctl().

Acho que você gosta

Origin blog.csdn.net/lczdk/article/details/123624664
Recomendado
Clasificación