Premiers pas avec Embedded Linux 3-File IO

descripteur de fichier

Chaque fichier Linux ouvert a un descripteur de fichier correspondant, et le descripteur de fichier est un entier non négatif. Nous pouvons obtenir un descripteur de fichier en appelant la fonction open().

Lorsque le shell démarre un processus, le processus hérite par défaut de trois descripteurs de fichier, appelés descripteurs de fichier standard, comme suit :

descripteur de fichier utiliser Nom POSIX flux stdio
0 entrée standard STDIN_FILENO standard
1 sortie standard STDOUT_FILENO sortie standard
2 erreur standard STDERR_FILENO stderr

Lorsque vous vous référez à ces descripteurs de fichiers dans le programme, vous pouvez utiliser le numéro directement, ou vous pouvez utiliser le nom POSIX défini dans <unistd.h> **(recommandé)**. Cependant, les descripteurs de fichiers correspondant au flux stdio ne sont pas statiques, ce sont juste leurs valeurs initiales, l'appel à la fonction freopen() peut changer la valeur du descripteur de fichier correspondant au flux stdio, référez-vous aux références suivantes :

Bien que les variables stdin, stdout et stderr se réfèrent initialement à l'entrée, à la sortie et à l'erreur standard du processus, thry peut être modifié pour faire référence à n'importe quel fichier en utilisant la fonction de bibliothèque freopen() . Dans le cadre de son fonctionnement, freopen() peut modifier le descripteur de fichier sous-jacent au flux rouvert. En d'autres termes, après un freopen() sur stdout, par exemple, il n'est plus sûr de supposer que le descripteur de fichier sous-jacent est toujours 1.

Fonctions d'E/S de fichiers

Les quatre principales fonctions d'appel système pour effectuer des E/S de fichier sont les suivantes (les langages et les progiciels utilisent généralement des fonctions d'E/S secondaires encapsulées pour les appeler indirectement, telles que les fonctions d'opération de fichier dans la bibliothèque C : fopen(), fread ( ), fécrire(), ffermer()) :

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以及与之相关的内核资源。

Voici le code source d'une commande cp simple, qui illustre l'application pratique des quatre fonctions ci-dessus.

/*
 * 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 à usage général

L'une des caractéristiques notables du modèle d'E/S UNIX est son concept universel d'entrée/sortie, ce que nous appelons souvent un fichier, et la fonction d'appel d'E/S système illustrée ci-dessus peut être utilisée pour effectuer des opérations d'E/S. sur n'importe quel fichier, par exemple, nous pouvons utiliser l'exemple de code ci-dessus comme ceci :

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

Autres fonctions courantes

lcherche()

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 */

image-20220320211322226

Cette fonction est utilisée pour changer le décalage du pointeur de lecture et d'écriture de fichier dans le descripteur de fichier. Pour chaque fichier ouvert, le noyau du système enregistre son décalage de fichier comme position de départ du fichier pour la prochaine opération write() ou read().

lseek ne fonctionne pas avec tous les types de fichiers et lseek() ne fonctionne pas avec les canaux, les FIFO, les sockets ou les terminaux. Lorsqu'il est utilisé avec le fichier ci-dessus, l'appel échoue et définit la variable globale errno sur ESPIPE.

Si le décalage de fichier du programme dépasse la fin du fichier, puis effectue des opérations d'E/S, la fonction read() renverra 0 pour indiquer la fin du fichier, et la fonction write() peut écrire des données à n'importe quelle position après la fin du fichier, ** L'espace entre la fin du fichier et les données nouvellement écrites est appelé un trou de fichier. **Pour le programme, cet espace est un contenu de fichier rempli de 0, mais pour le disque, cet espace n'occupe aucun espace disque (il y aura donc des cas où la taille du fichier sera supérieure à l'espace disque occupé).

Voici un exemple de programme qui illustre l'utilisation de lseek en conjonction avec la lecture et l'écriture. Les routines du "Manuel de programmation du système UNIX" utilisent de nombreuses fonctions de bibliothèque écrites par moi-même. Je les ai simplement remplacées afin qu'elles puissent être exécutées directement. Non besoin de télécharger le code source, bien sûr, il n'est pas si "robuste". . .

/*
 * 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);
}

Le processus de commande shell suivant illustre l'effet d'exécution de l'exemple de programme ci-dessus et le phénomène de fichier vide.

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为特定的操作请求,后面会跟一个或多个操作相关的参数,也可能没有

L'argument fd est un descripteur de fichier ouvert pour le périphérique ou le fichier sur lequel l'opération de contrôle spécifiée par la demande doit être effectuée. Les fichiers d'en-tête spécifiques au périphérique définissent des constantes qui peuvent être transmises dans l'argument de requête. Comme indiqué par la notation standard C points de suspension (…), le troisième argument de ioctl(), que nous étiquetons argp, peut être de n'importe quel type. La valeur de l'argument request permet à ioctl() de déterminer le type de valeur à attendre dans argp. Typiquement, argp est un
pointeur vers un entier ou une structure ; dans certains cas, il n'est pas utilisé.

Résumer

Pour les opérations ordinaires sur les fichiers, nous utilisons généralement d'abord la fonction open() pour ouvrir le fichier et obtenir le descripteur de fichier, puis utilisons la fonction read()/write()/lseek() pour effectuer les opérations associées, et enfin close() pour fermer le fichier.

Pour toutes les opérations sur les périphériques et les fichiers non incorporées dans le modèle général d'E/S, utilisez la fonction ioctl().

Je suppose que tu aimes

Origine blog.csdn.net/lczdk/article/details/123624664
conseillé
Classement