文件I/O

  文件I/O也称系统调用I/O,是操作系统“用户态”运行的进程和硬件交互提供的一组接口,即操作系统内核留给用户程序的一个接口。
  在C语言中常见的输入输出函数(scanf,printf)实际上都是调用了文件I/O函数,因为系统API效率高,一次性读取。

一、缓存与IO

  下图以fgets / fputs 示意了I/O缓冲区的作用,使用fgets / fputs 函数时在用户程序中也需要分配缓冲 区(图中的buf1和buf2 ),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。
在这里插入图片描述

  • I/O缓存区:用户进程空间

  • 内核缓冲区 :内核层

  • 磁盘缓存 大概的基本流程标准

  应用内核缓冲技术导致的结果是:提高了磁盘的I/O效率;一次读入大量的数据放在缓冲区,需要的时候从缓冲区取得数据。减少系统调用次数read和write等

二、函数open(打开/创建 一个文件/设备)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);

1、参数解析

第一个参数:字符串形式的路径和文件名
第二个参数:操作标志

O_RDONLY    只读模式
O_WRONLY    只写模式
O_RDWR      读写模式
O_EXEC      只执行打开
O_SEARCH    只搜索打开(应用于目录)

  上述 5 种标志位是互斥的,也就是不可同时使用,但可与下列的标志位利用 OR(|) 运算符组合。
第三个参数:操作模式

O_APPEND:每次写时追加到文件的尾端。
O_CLOEXEC:把FD_CLOEXEC常量设置为文件描述符标志。
O_CREAT:若此文件不存在则创建它。使用此选项时,open函数需同时说明第3个参数mode(openat函数需说明第4个参数mode),用mode指定该新文件的访问权限位。
O_DIRECTORY:如果path引用的不是目录,则出错。
O_EXCL:如果同时指定了O_CREAT,而文件已经存在,则出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件,这使测试和创建两者称为一个原子操作。
O_NOCTTY:如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端。
O_NOFOLLOW:如果path引用的是一个符号链接,则出错。
O_NONBLOCK:如果path引用的是一个FIFO、一个特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后序的I/O操作设置非阻塞方式。
O_SYNC :使每次write等待物理I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O。
O_TRUNC:如果此文件存在,而且为只读或读-写成功打开,则将其长度截断为0。
O_TTY_INIT:如果打开一个还未打开的终端设备,设置非标准termios参数值,使其符合Single UNIX Specification(以及POSIX.1)中同步输入和输出选项的一部分。
O_DSYNC:使每次write要等待物理I/O操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需要等待文件属性被更新。
O_RSYNC:使每一个以文件描述符作为参数进行的read操作等待,直至所有对文件同一部分挂起的写操作都完成。

2、demo

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

#define buff_size 1024
#define FLAGS1 O_CREAT // O_CREAT表示没有文件则创建,并赋予权限,如果存在则覆盖
#define FLAGS2 O_CREAT | O_RDWR // O_RDWR表示以读写的方式打开文件
#define FLAGS3 O_RDWR | O_APPEND // O_APPEND表示在文件中追加内容
#define FLAGS4 O_CREAT | O_RDWR | O_EXCL // O_EXCL表示文件存在则返回-1


int main(int argc, char *argv[])
{
    int fd;
    char buf[buff_size] = "World";
    if (argc < 2) //如果没有传递文件名,则退出
    {
        printf("./app filename\n");
        exit(1); //不管在任何函数中,exit(1)都会退出,和return不一样
    }
    //fd = open(argv[1], O_CREAT, 0644);
    fd = open(argv[1], FLAGS4);
    // 向打开的文件中写入数据
    write(fd, buf, strlen(buf));
    printf("fd = %d\n", fd);
    // 关闭文件
    close(fd); 
    return 0;
}

三、函数read(从打开的文件中读取数据)

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

1、参数解析

第一个参数:文件描述符
第二个参数:缓冲区的首地址
第三个参数:读取的数据大小

2、返回值

  成功返回读取到的数据大小,若已到文件尾,返回 0,失败返回 -1。很多种情况可使实际读到的字节数少于要求读的字节数:
(1)读普通文件时,在读到要求字节数之前已经到达了文件尾端。
(2)当从终端设备读时,通常一次最多读一行。
(3)当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
(4)当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
(5)当从某些面向记录的设别(如磁带)读时,一次最多返回一个记录。
(6)当一信号造成中断,而已经读了部分数据量时。

3、demo

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

#define buff_size 1024
#define FLAG O_CREAT | O_WRONLY | O_TRUNC

int main(int argc, char *argv[])
{
    char buf[buff_size];
    int fd_src, fd_dest, len;
    if (argc < 3)
    {
        printf("./mycpp src dest\n");
        exit(1);
    }
    fd_src = open(argv[1], O_RDONLY);
    fd_dest = open(argv[2], FLAG, 0644);
    // 成功则返回读到的字节数;读到文件末尾返回0;读失败则返回-1
    while ((len = read(fd_src, buf, buff_size)) > 0)
        if (write(fd_dest, buf, len) != len) // write返回读到的字节数
            printf("write error\n");
    if (len < 0)
        printf("read error\n");
    close(fd_src);
    close(fd_dest);
    exit(0);
}

四、函数write(从打开的文件中写入数据)

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

1、参数解析

第一个参数:文件描述符
第二个参数:缓冲区首地址
第三个参数:数据的长度

2、返回值

  成功返回写入的数据长度,失败返回 -1。其返回值通常与 count 的值相同,否则表示出错。write 出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。对于普通文件,写操作从文件的当前偏移量处开始。如果在打开文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。

五、函数lseek(文件偏移量)

#include <sys/types.h>
#include <unistd.h>
off_t lseek (int fd, off_t offset, int whence);

1、参数解析

第一个参数:文件描述符
第二个参数:偏移量
第三个参数:从什么地方开始偏移

  若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节;若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可为正或负;若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可正可负。
  偏移起始位置:**文件头0(SEEK_SET),当前位置1(SEEK_CUR),文件尾2(SEEK_END))**为基准,偏移offset(指针偏移量)个字节的位置。

2、返回值

  若成功,返回新的文件偏移量;若出错,返回为 -1。

3、demo

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

int main(int argc, char *argv[])
{
    int fd = open("abc.txt", O_RDWR); // 打开一个文件
    if (fd < 0)
    {
        perror("open abc"); // 如果打开不成功则报错,并打印报错信息
        exit(1);
    }
    lseek(fd, 0x1000, SEEK_SET); // 拓展文件,一定要有一次写操作;
    write(fd, "a", 1); // 写入一个字节
    close(fd);
    fd = open("edf.txt", O_RDWR);
    if (fd < 0)
    {
        perror("open edf"); // 如果打开不成功则报错
        exit(1);
    }
    printf("file size = %d\n", lseek(fd, 0, SEEK_END)); // 获取文件大小
    close(fd);
    return 0;
}

六、函数fcntl(改变已打开文件的属性)

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

1、参数解析

第一个参数:文件描述符
第二个参数:操作的命令

F_DUPFD/F_DUPFD_CLOEXEC:复制文件描述符的功能,寻找最小的有效的大于等于第三个参数 arg 的描述符作为新的描述符。与 dup2 函数有所不同的是,不会强制关闭已经被占用的描述符。
F_GETFD/F_SETFD:获取/设置文件描述符的标志。
F_GETFL/F_SETFL:获取/设置文件状态的标志。
F_GETOWN/F_SETOWN:获取/设置异步 I/O 所有权。
F_SETLK/F_GETLK:加锁/解锁/测试锁是否存在。

第三个参数:可变长参数

2、返回值

F_DUPFD:成功返回新的文件描述符
F_GETFD:成功返回文件描述符的标志值
F_GETFL:成功返回文件状态的标志值
F_GETOWN:成功返回异步 I/O 所有权
其他操作成功返回 0, 所有的操作失败返回 -1

3、函数功能

(1)复制文件描述符。
(2)获取/设置文件描述符的标志。
(3)获取/设置文件状态的标志。
(4)获取/设置异步 I/O 所有权。
(5)实现文件锁的功能。

4、demo

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define MSG_TRY "try again\n"

int main(int argc, char *argv[])
{
    char buf[1024];
    int flags;
    flags = fcntl(STDIN_FILENO, F_GETFL); // 获取标准输入的属性
    flags |= O_NONBLOCK; // 改变属性
    if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) // 重新设置新的属性
    {
        perror("fcntl");
        exit(1);
    }
    int i;
    for (i = 0; i < 5; ++i) // 设置阻塞条件,只轮询五次
    {
        n = read(fd, buf, buf_size); // 如果不阻塞则读文件
        if (n > 0) // 读文件成功则退出
            break;
        if (errno != EAGAIN) // EAGAIN表示再尝试一次,可能是没有数据
        {
            perror("read /dev/tty");
            exit(1);
        }
        sleep(1); // 睡眠一秒
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); // 打印一句话到标准输出
    }
    if (i == 5 ) // 轮询之后还是没有读取到数据,则打印超时
        write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT)); // 打印一句话到标准输出
    else
        write(STDOUT_FILENO, buf, n); // 读取到了数据,写到标准输出
    close(fd);
    exit(0);
}

七、函数ioctl(获取设备的属性)

int ioctl(int fd, ind cmd,)

1、参数解析

  其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。

2、demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
    int fd;
    struct winsize size;
    if (isatty(STDOUT_FILENO) == 0)
        exit(1);
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0)
    {
        perror("ioctl error");
        exit(1);
    }
    printf("R = %d\nC = %d", size.ws_row, size.ws_col); // 获取终端屏幕的大小
    return 0;
}

八、函数dup和dup2(复制现有的文件描述符)

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

1、返回值

  成功返回新的文件描述符,失败返回 -1。

2、Note

  文件描述符的复制本质上就是让多个文件描述符对应同一个文件表,也就是对应同一个文件。由 dup 返回的心的文件描述符一定是当前可用文件描述符中的最小数值。

  newfd 作为参数 oldfd 的拷贝,如果 newfd 已经打开,则先将其关闭;如若 oldfd 等于 newfd,则 dup2 返回 newfd,而不关闭它。否则,newfd 的 FD_CLOEXEC 文件描述符标志被清除,这样 newfd 在进程调用 exec 时是打开状态。

3、demo

//dup/dup2函数的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
int main(void)
{
	//1.打开/创建一个文件
	int fd = open("d.txt",O_RDWR|O_CREAT/*|O_EXCL*/,0644);
	if(-1 == fd)
	{
		perror("open"),exit(-1);
	}
	printf("fd = %d\n",fd);//3
	//2.使用dup函数复制文件描述符
	// int fd2 = fd;
	int fd2 = dup(fd);
	if(-1 == fd2)
	{
		perror("dup"),exit(-1);
	}
	printf("fd2 = %d\n",fd2);//4
	//3.针对不同的描述符进行处理
	write(fd,"A",1);
	write(fd2,"a",1);
 
	//打开/创建一个新文件
	int fd3 = open("e.txt",O_RDWR|O_CREAT,0644);
	if(-1 == fd3)
	{
		perror("open"),exit(-1);
	}
	printf("fd3 = %d\n",fd3);//5
	
	//使用dup2函数进行描述符的拷贝
	// fd 到 fd3的拷贝 fd3 和 fd4相等
	int fd4 = dup2(fd,fd3);
	printf("fd3 = %d,fd4 = %d\n",fd3,fd4);// 5 5 
 
	write(fd3,"1",1); //d.txt
	write(fd4,"2",1); //d.txt
	write(fd,"3",1); //d.txt
 
	//4.关闭所有描述符
	close(fd);
	close(fd2);
	close(fd3);
	return 0;
}

九、阻塞I/O与非阻塞I/O

1、简介

(1)IO请求的两个阶段:

1. 等待资源阶段:IO请求一般需要请求特殊的资源(如磁盘、RAM、文件),当资源被上一个使用者使用没有被释放时,IO请求就会被阻塞,直到能够使用这个资源;

2. 使用资源阶段:真正进行数据接收和发生。

(2)在等待数据阶段,IO分为阻塞IO和非阻塞IO:

1. 阻塞IO: 资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时);

2. 非阻塞IO:资源不可用时,IO请求离开返回,返回数据标识资源不可用。

(3)在使用资源阶段,IO分为同步IO和异步IO:

1. 同步IO:应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败;

2. 异步IO:应用发送或接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或接收,并返回成功或失败的信息给应用。

2、阻塞I/O

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

int main(int argc, char *argv[])
{
    char buf[1024];
    // STDIN_FILENO表示从标准输入读取数据。
    int len = read(STDIN_FILENO, buf, sizeof(buf)); 
    // STDOUT_FILENO表示写到标准输出,如果没有从标准输入接收到数据,
    // 则标准输出会一直等待。
    write(STDOUT_FILENO, buf, len);  
    exit(0);
}

3、非阻塞I/O

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

#define FLAG O_RDONLY | O_NONBLOCK
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n"
#define buf_size 1024

int main(int argc, char *argv[])
{
    char buf[buf_size];
    int fd, n;
    fd = open("/dev/tty", FLAG); // 以非阻塞方式打开一个文件
    if (fd < 0)
    {
        perror("open /dev/tty"); // 如果打开失败,则打印失败原因,退出程序
        exit(1);
    }
    int i;
    for (i = 0; i < 5; ++i) // 设置阻塞条件,只轮询五次
    {
        n = read(fd, buf, buf_size); // 如果不阻塞则读文件
        if (n > 0) // 读文件成功则退出
            break;
        if (errno != EAGAIN) // EAGAIN表示再尝试一次,可能是没有数据
        {
            perror("read /dev/tty");
            exit(1);
        }
        sleep(1); // 睡眠一秒
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); // 打印一句话到标准输出
    }
    if (i == 5 ) // 轮询之后还是没有读取到数据,则打印超时
        write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT)); // 打印一句话到标准输出
    else
        write(STDOUT_FILENO, buf, n); // 读取到了数据,写到标准输出
    close(fd);
    exit(0);
}

参考:https://blog.csdn.net/qq_29350001/article/details/66969233
https://blog.csdn.net/yyxyong/article/details/62894064

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~青萍之末~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值