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 */
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().