文件IO
基础IO和标准IO
# API
open/close/read/write
lseek/fcntl/ioctl/mmap 通过lseek获取文件的大小:lseek(fd, 0, SEEK_END); fcntl函数改变一个已经打开的文件的属性,而不必重新open文件,可以重新设置读、写、追加、非阻塞等标志。通过F_GETFL 和 F_SETFL。 ioctl函数用于向设备发控制和配置命令 mmap磁盘映射
# 阻塞与非阻塞IO
https://blog.csdn.net/CSDN_logo/article/details/46500065 阻塞(Block):当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其他进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep的睡眠时间到了),它才有可能继续运行。 与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:
- 正在被调度执行:CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,正在执行该进程的指令,正在读写该进程的地址空间。
- 就绪状态:该进程不需要等待什么事件发生,随时都可以执行 ,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。
如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。以read为例,如果设备暂时没有数据可读就返回-1,同时置errno为EWOULDBLOCK(或者EAGAIN,这两个宏定义的值相同),表示本来应该阻塞在这里,事实上并没有阻塞,而是直接返回错误,调用者应该试着再读一次,这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备。
# 控制缓冲
标准库函数在用户空间的缓冲区 全缓冲-缓冲区满了触发调用系统调用 行缓冲-缓冲区满了、或者遇到换行,调用系统调用 -标准输出是行缓冲 无缓冲-stderr是无缓冲
# perror与strerror
#include <string.h>
char *strerror(int errnum);
//作用:把其中一个错误代码作为strerror作为参数,并返回一个用于描述错误的字符串的指针,简言之就是将错误码转换成错误提示(错误码变成字符串提示)。
#include <stdio.h>
void perror(const char *str)
//perror以一种简单,统一的方式报告错误,将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。基于errno的当前值,在标准出错上产生一条出错信息,然后返回。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *pfile;
pfile = fopen("unexist.ent", "rb");
if (pfile == NULL)
{
//perror("pfile");
printf("%d:%s\n", errno, strerror(errno));
}
else
fclose(pfile);
system("pause");
return 0;
}
//perror是将errno对应的错误消息的字符串打印到标准错误输出上,即stderr或2上,若你的程序将标准错误输出重定向到/dev/null,那就看不到了,就不能用perror了。而 strerror的作用只是将errno对应的错误消息字符串返回,要怎样处理完全由你自己决定。通常我们选择把错误消息保存到日志文件中,即写文件,所以通常可以用fprintf(fp, "%s", strerror(errno))将错误消息打印到fp指向的文件中。其中perror中errno对应的错误消息集合跟strerror是一样的,也就是说不会漏掉某些错误。
# 文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。当打开(open)一个现有文件或创建(creat)一个新文件时,内核向进程返回一个文件描述符。
UNIX系统shell把文件描述符0与进程的标准输入关联,文件描述符1与进程的标准输出关联,文件描述符2与进程的标准错误关联。即文件描述符0、1、2已被标准化,替换成符号常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,这些常量都在头文件<unistd.h>中定义。
# 函数open和openat
打开或创建一个文件。
#include <fcntl.h>
int open(const char* path, int flag, .../* mode_t mode */);
int openat(int dirfd, const char* path, int flag, .../* mode_t mode */);//一般不用
the argument flag must include one of following access modes:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
下面的常量是可选的:
O_APPEND 每次写时都追加到文件的尾端
O_CREAT 若文件不存在则创建,使用此选项时,open函数需同时说明第3个参数mode,用mode指定该新文件的访问权限
O_TRUNC 如果此文件存在,而且为只写或读写成功打开,则将其长度截断为0
# 函数creat
创建一个新文件
#include <fcntl.h>
int creat(const char* path, mode_t mode);
等效于:
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
# 函数close
关闭一个打开的文件
#include <unistd.h>
int close(int fd);
关闭文件还会释放该进程加在该文件上的所有记录锁。
# 函数lseek
每个打开文件都有一个与其相关联的“当前文件偏移量”。通常,读、写操作都是从当前文件偏移量处开始,并使偏移量增加所读写的字节数。 lseek显式地为一个打开文件设置偏移量。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
对参数offset的解释与参数whence的值相关:
若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。
若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可正可负。
若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负。
如果文件描述符指向的是一个管道、FIFO或网络套接字,则lseek返回-1.
#include <unistd.h>
#include <stdio.h>
int main()
{
if(-1 == lseek(STDIN_FILENO, 0, SEEK_CUR))
printf("error\n");
else
printf("ok\n");
return 0;
}
// ./a.out < strim.cpp 输出 ok
// cat strim.cpp | ./a.out 输出 error,由于是从管道读取
# 函数read
从打开文件中读取数据
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);
// 如read成功,返回读到的字节数
// 如已到达文件的尾端,返回0
# 函数write
向打开文件写数据
#include <unistd.h>
ssize_t write(int fd, void* buf, size_t nbytes);
// 返回值通常与参数nbytes的值相同,否则表示出错
# 文件共享
超级好文:https://blog.csdn.net/qq_28114615/article/details/94590598
内核用于所有I/O的数据结构 内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
- 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
- 文件描述符标志
- 指向一个文件表项的指针
- 内核为所有打开文件维持一张文件表。每个文件表项包含:
- 文件状态标志(读、写、读写、同步、非阻塞等)
- 当前文件偏移量
- 指向该文件v节点表项的指针
- 每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息是打开文件时从磁盘上读入内存的。
多个进程写同一文件,会产生预想不到的结果。为避免这种情况,需使用原子操作。
# 函数dup和dup2
复制一个现有的文件描述符
#include <unistd.h>
// 返回的新文件描述符一定是当前可用文件描述符中的最小数值
int dup(int fd);
// 如果fd2已经打开,先将其关闭
// 若fd等于fd2,则dup2返回fd2,不关闭它。否则fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态。
int dup2(int fd, int fd2);
# 函数fcntl
改变已经打开文件的属性
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */)
fcntl函数有以下5种功能:
- 复制一个已有的描述符 (cmd=F_DUPFD或F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标志 (cmd=F_GETFD或F_SETFD)
- 获取/设置文件状态标志 (cmd=F_GETFL或F_SETFL),文件状态指open时的状态 可以更改的标志是:O_APPEND、O_NONBLOCK、O_SYNC、O_DSYNC、O_RSYNC、O_FSYNC、O_ASYNC。
- 获取/设置异步I/O所有权 (cmd=F_GETOWN或F_SETOWN)
- 获取/设置记录锁 (cmd=F_GETLK、F_SETLK或F_SETLKW)
#include "apue.h"
#include <fcntl.h>
int main(int argc, char* argv[])
{
int val;
if(argc != 2)
err_quit("usage: a.out <descriptor#>");
if((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
err_sys("fcntl error for fd %d", atoi(argv[1]));
switch(val & O_ACCMODE){
case O_RDONLY:
printf("read only");
break;
case O_WRONLY:
printf("read only");
break;
case O_RDWR:
printf("read only");
break;
default:
err_dump("unknown access mode");
}
if(val & O_APPEND)
printf(", append");
if(val & O_NONBLOCK)
printf(", nonblocking");
if(val & O_SYNC)
printf(", synchronous writes");
#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
if(val & O_FSYNC)
printf(", synchronous writes");
#endif
putchar('\n');
exit(0);
}
// 执行结果
$./a.out 0 < /dev/tty
read only
$./a.out 1 > temp.foo
$cat temp.foo
write only
$./a.out 2 2>>temp.foo
write only, append
$./a.out 5 5<>temp.foo
read write
// 5<>temp.foo 表示在文件描述符5上代开文件temp.foo以供读、写。
在修改文件描述符标志或文件状态标志时,先要获得现在的标志值,然后修改,最后设置新标志值。
#include "apue.h"
#include <fcntl.h>
void set_fl(int fd, int flags)
{
int val;
if((val = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("fcntl F_GETFL error");
val |= flags; // turn flags off
if((val = fcntl(fd, F_SETFL, val)) < 0)
err_sys("fcntl F_SETFL error");
}
# 函数ioctl
操作IO
#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);