第二十八讲 IO 函数


文章目录

  • 第二十八讲 IO 函数
  • 一、 文件描述符和打开模式
  • 1、常见文件操作函数
  • 2、文件打开模式
  • 二、 标准 io 函数
  • 1、 常见的标准io函数
  • 2、文件io五大模式
  • 三、控制 led 设备
  • 1、驱动程序
  • 本质
  • 步骤
  • 四、实验代码(驱动红色led小灯)
  • 五、 其他


本讲是联合几讲的内容,因为前面每一期内容都很少,所以结合起来了

本章会有一些扩展的知识,可能会有写偏差,但是会更详细一些,是参考了网上大佬们的理解。很感谢大佬们。

参考链接

一、 文件描述符和打开模式

1、常见文件操作函数

函数

原型

参数及其说明

返回值

头文件

open

(打开文件)

int open(const char *pathname, int flags);

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

pathname:表示要打开的文件路径。

flags:用于指示打开文件的选项,常用的有 O_RDONLY、O_WRONLY 和 O_RDWR。这三个选项必须有且只能有一个被指定。

mode:只在创建文件时需要,用于指定所创建文件的权限位(还要受到 umask 环境变量的影响)。

成功:文件描述符

失败:-1

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

close

(关闭文件)

int close(int fd)

fd:文件描述符

成功:0

失败:-1

#include <unistd.h>

read

(读取文件内容)

ssize_t read(int fd,void *buff,size_t count)

fd:使用 open 打开的文件的文件描述符

buff:读取的文件内容

count:需要读取的长度

成功:

count:成功读取全部字节

0~count:

剩余文件长度小于count

读取期间被异步信号打断

失败:

-1,读取错误

#include <unistd.h>

write

(写入文件)

ssize_t write(int fd,void *buff,size_t count)

fd:使用 open 打开的文件的文件描述符

buff:写入的文件内容

count:需要写入文件的长度

成功:

count:成功写入全部字节

0~count:

写入期间被异步信号打断

失败:

-1,读取错误

#include <unistd.h>

lseek

(设置文件读写位置)

off_t lseek(int fd,off_t offset,int where)

fd:使用 open 打开的文件的文件描述符

offset:偏移量

where:偏移量基准点

成功:文件偏移位置值

失败:-1

#include <unistd.h>

sync

(页缓存和回写)

void sync(void);



#include <unistd.h>

2、文件打开模式

选项

说明

O_APPEND

每次进行写操作时,内核都会先定位到文件尾,再执行写操作。

O_ASYNC

使用异步 I/O 模式。

O_CLOEXEC

在打开文件的时候,就为文件描述符设置 FD_CLOEXEC 标志。这是一个新的选项,用于解决在多线程下 fork 与用 fcntl 设置 FD_CLOEXEC 的竞争问题。某些应用使用 fork 来执行第三方的业务,为了避免泄露已打开文件的内容, 那些文件会设置 FD_CLOEXEC 标志。但是 fork 与 fcntl 是两次调用,在多线程下, 可能会在 fcntl 调用前,就已经 fork 出子进程了,从而导致该文件句柄暴露给子进程。关于 O_CLOEXEC 的用途

O_CREAT

当文件不存在时,就创建文件

O_DIRECT

对该文件进行直接 I/O,不使用 VFS Cache。

O_DIRECTORY

要求打开的路径必须是目录

O_EXCL

该标志用于确保是此次调用创建的文件,需要与 O_CREAT 同时使用; 当文件已经存在时,open 函数会返回失败。

O_LARGEFILE

表明文件为大文件

O_NOATIME

读取文件时,不更新文件最后的访问时间

O_NONBLOCK<br> O_NDELAY

将该文件描述符设置为非阻塞的(默认都是阻塞的)

O_SYNC

设置为 I/O 同步模式,每次进行写操作时都会将数据同步到磁盘,然后write 才能返回

O_TRUNC

在打开文件的时候,将文件长度截断为0,需要与O_RDWR或O_WRONLY同时使用。在写文件时,如果是作为新文件重新写入,一定要使用O_TRUNC标志,否则可能会造成旧内容依然存在于文件中的错误,如生成配置文件、pid文件等

3、lseek where 参数说明

参数

说明

SEEK_SET

基准点为文件开头

SEEK_CUR

基准点为当前位置

SEEK_END

基准点为文件末尾

二、 标准 io 函数

c 标准库实现了一个 IO缓存区。通过调用标准io函数讲数据写入io缓存区,当数据达到一定数量或者调用fflush 函数才会将数据写入内核空间的页缓存区。同理,当页缓存区满了或者调用sync函数才能讲数据写入磁盘。他们的调用关系如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOIgj9cX-1632844288613)(C:\Users\Dragon\Desktop\野火linux学习笔记\第一期\第二十八讲.assets\image-20210924232307432.png)]

1、 常见的标准io函数

函数

说明

fopen

打开文件

fclose

关闭文件

fread

读取文件

fwrite

写入文件

fseek

设置文件读写位置

fflush

同步文件进入页缓冲区

2、文件io五大模式

模式

说明

非阻塞模式

当需要读写文件时,即使文件没法正常读写,也必须立马返回,不等待文件就绪

阻塞模式

当需要读写文件时,文件没法正常读写时,等待文件就绪,然后进行正常读写

io多路复用

IO多路复用主要有select、poll、epoll三种模式,select/poll相差不大,主要是通过轮询来不断的检测是否有描述符已就绪,select默认情况下支持最多监控1024个描述符,poll则没有这个限制(底层通过链表实现,可动态增加);epoll不是通过轮询,而是通过回调(callback)方式主动通知已有描述符已就绪,相比较select/poll效率有明显提升。

异步io

读写文件的时候,只需要将数据发送过去,系统会将数据进行读写,然后发送信号告诉执行的读写状态

信号驱动io

等待文件读写就绪状态,当文件就绪时,系统发送信号量通知用户可以读写文件,然后用户再进行读写

参考链接

三、控制 led 设备

1、驱动程序

本质

为硬件设备创建相应的设备节点文件;创建设备文件时,规定好设备文件的使用方式。

根据驱动程序规定的设备文件的使用方式去控制硬件。

步骤

1、找出硬件设备所对应的设备节点文件(/dev 或者 /sys 目录下)

  • led 设备节点文件目录
    /sys/class/leds

2、找出驱动程序规定的设备文件的使用方式

  • 往 brightness 文件写入数值

四、实验代码(驱动红色led小灯)

/*
 * @LastEditors: 夜雨
 * @Date: 2021-09-26 22:23:56
 * @LastEditTime: 2021-09-28 23:10:02
 * @FilePath: /001led/operations_led.c
 */

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

#define LEDS_ROOT_PATH    "/sys/class/leds/"
#define LEDS_OPTION_FILE "/brightness"
#define LEDS_CUR_PATH(rootpath, color, operationfile)    ""rootpath""color""operationfile""
#define LEDS_PATH(color) LEDS_CUR_PATH(LEDS_ROOT_PATH, color, LEDS_OPTION_FILE)

int main(char* argc, char**argv)
{ 
    int redFd = open(LEDS_PATH("red"), O_WRONLY);
    {
        if(redFd == -1)
        {
            printf("open %s is failed",LEDS_PATH("red") );
            close(redFd);
            while(1);
        }
    }
    while(1)
    {
        write(redFd, "255", 3) ;
        printf("write 255\r\n" );
        sleep(1);
        write(redFd, "0", 1) ;
        printf("write 0\r\n" );
        sleep(1);
    }
    

    return 0;
}

五、 其他

本章讲到的所有函数并没有都将例子举出来,如果感兴趣,可以自己去实践下。当然,如果您仅仅只看文章,不去敲代码,其实是学不到什么东西的。并且这种要经常用才能加深自己的印象。

在实验代码中,我只做了红灯实验,感兴趣的可以将 open(LEDS_PATH("red"), O_WRONLY) 改成 open(LEDS_PATH("green"), O_WRONLY) 或者 open(LEDS_PATH("blue), O_WRONLY) 试试看其他灯的效果。如果你还想玩,可以改改亮灭函数控制不同灯亮的方法。