Linux系统下I/O

一、I/O简介

I/O(输入/输出)是在主存和外部设备(磁盘驱动器、网络、终端)之间复制数据的过程。输入是从外部设备复制到主存,输出是从主存复制到外部设备。
在Linux系统中所有的I/O设备都被映射称为文件,所有的输入输出都被当做相应文件的读和写来执行,所以内核提供了系统级的I/O函数接口,使得所有输入输出都以统一且一致的方式来执行。

  1. 打开文件,返回一个非负整数,叫做描述符
  2. 每个进程都默认打开三个描述符,标准输入 STDIN_FILENO(描述符0)、标准输出 STDOUT_FILENO(描述符1)、标准出错 STDERR_FILENO(描述符2)。
  3. 读写文件,读就是从文件复制n个字节到内存,写就是从内存复制n个字节到文件。
  4. 文件偏移:默认打开文件是从文件开头起始的字节偏移量,可以使用seek来操作。
  5. 关闭文件。

今天从四个方面来说I/O,文件I/O、标准I/O库、高级I/O、终端I/O。

  1. 文件I/O: 文件的打卡、读写、关闭、偏移。
  2. 标准I/O库:Linux提供的标准I/O库函数
  3. 高级I/O:非阻塞I/O、I/O多路转接、异步I/O
  4. 终端I/O: 更改终端属性操作的函数

二、文件I/O

Linux系统中文件I/O一般只用到以下五个函数:open、read、write、lseek、close。每次read、write都是一次系统调用(从用户层拷贝到内核层再拷贝到用户层)且不带缓冲。
1. 文件描述符
对于内核而言,每个打开的文件都是通过文件描述符引用的,每个文件描述符都是一个非负整数,打开或者创建一个文件都会返回一个文件描述符,通过这个文件描述符来进行读写,
2. 打开/创建文件

 #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);功能:创建或者打开一个文件,返回一个文件描述符参数: pathname:路径名/文件名flags:标志位O_RDONLY 只读O_WRONLY 只写O_RDWR 既可以读也可以写O_APPEND 以追加的方式操作文件O_CREAT  如果文件不存在,则创建O_TRUNC 如果文件存在,则清空文件的数据O_EXCL   表示文件已经存在,而又重复创建一次,open函数会返回错误,返回文件已经存在的错误,对错误做处理之后,直接打开文件就可以O_APPEND   从文件末尾位置追加写入O_SYNC    每次write等物理I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O,(后边会用到)O_RSYNC 每个以文件描述符作为参数进行的read操作等待,直到所有对文件同一部分挂起的写操作都完成。mode:如果是创建一个文件,需要添加对应文件的属性,模式属性一般用一个八进制数代替,如果属性成立,为1,不成立,则为0rwxr-x-wx --> 0753rw-rw-r-- --> 0664返回值:成功:文件描述符失败:-1

3. 关闭文件
关闭一个文件时会自动释放加在该文件上的所有锁,当进程终止时会自动关闭所有打开的文件。

 #include <unistd.h>int close(int fd);参数:fd:open返回的文件描述符返回值:成功 0, 失败 -1.

4. 文件偏移
通常所有读写操作都是从当前文件偏移量处开始,并使偏移量增加读写的字节数,默认是0。可以使用lseek显式打开文件设置偏移量。

 #include <sys/types.h>#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);参数:fd :    open函数打开的文件 offset:  与whence有关whence:     基准点SEEK_SET 将读写位置指向文件头后再增加offset个位移量。 SEEK_CUR 以目前的读写位置往后增加offset个位移量。 SEEK_END 将读写位置指向文件尾后再增加offset个位移量(使用该参数可以算出文件字节数)当whence 值为SEEK_CUR 或SEEK_END时,参数offet允许负值的出现。返回值:成功,返回文件偏移量,失败 -1.注释:文件偏移量可以大于文件长度,这样就会构成空洞文件,对于多出的这些字节被读出为0.空洞文件在磁盘中不占用存储区。

5. 读文件

 #include <unistd.h>ssize_t read(int fd, void *buf, size_t count);参数:fd:文件描述符buf:读取到的数据const:每一次最多读取到的字节数返回值:成功:读取的字节数   如果是0 代表结尾失败:-1

6. 写文件

 #include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);功能:向一个文件描述符写数据参数:fd:文件描述符buf:要写入的数据const:每一次最多写入到的字节数返回值:成功:写入的字节个数失败:-1失败原因多是磁盘已满或者超过一个给定进程的文件长度限制。

7. 文件共享
Linux系统支持不同进程间共享打开文件,在此先说一下内核用于所以I/O的数据结构。
内核使用三种数据结构表示打开的文件,他们之间的关系决定了文件共享中一个进程对另一个进程的影响。
首先每个进程在进程表中有一个记录项,每个记录项包含一张打开的文件描述符,每个描述符占用一项,与文件描述符有关的是:

  1. 文件描述符标志

  2. 指向文件表项的指针
    其次内核为每个打开文件维持一张文件表,文件表项包含:

    1. 文件状态标志(读、写、阻塞等)
    2. 当前文件偏移量
    3. 指向该文件v节点表项指针
      最后每个打开文件(设备)都有一个v节点结构,它包含了:

      1. 文件类型
      2. 对该文件进行各种操作的指针。
      3. i节点(i-node),包含了文件的长度、所以者、指向文件实际数据块在磁盘的位置。

    这些信息都是在打开文件时候从磁盘拷贝到内存,所以这些信息都是随时可用的。总结一下这三张表关系

     进程表项:  fd标志文件指针(文件表项):文件状态标志当前文件偏移量v节点指针(v节点表项):   v节点信息v_data:    i节点(i节点表项): i节点信息当前文件长度等
    

    了解了内核的这三个数据结构之后我们回过头来看文件共享。
    假定一个进程打开了一个文件,返回文件描述符是4,另一个进程也打开了这个文件描述符返回的文件描述符是5,打开该文件的每个进程都有一个文件表项(进程对该文件的当前偏移量),但是该文件只有一个v节点。

    1. 每当write之后,文件表项中担负起偏移量会增加写入的字节数,如果当前文件偏移量超出了当前文件长度则i节点表项中文件长度也增加。
    2. 如果使用O_APPEND打开一个文件,相应的标志被设置到文件表项中的文件状态标志,每次对该文件写操作时,文件表项中当前文件偏移量会被设置为i节点表项的文件长度。
    3. 当使用lseek函数定位到文件尾端时候,文件表项中的当前文件偏移量被设置为i节点表项中的文件长度。
    4. 存在多个文件描述符指向同一个文件的情况。

    6. 原子操作
    当有多个进程操作一个文件时候为了数据同步Linux系统提供了原子操作。

    1. open一个文件时候使用 O_APPEND 标志

    2. 使用pread 和 pwrite 函数
      pread/pwrite 相当于调用lseek之后调用read/write,但是区别在于调用pread/pwrite时,无法中断其定位和读写操作,而且不更新当前文件的偏移量。

        #include <unistd.h>ssize_t pread(int fd, void *buf, size_t count, off_t offset);功能:读文件参数:fd:文件描述符buf:读缓冲区count:缓冲区大小offset:偏移量返回值: 成功:读到的字节数,如果读到文件尾返回0, 失败-1ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);功能:写文件参数:fd:文件描述符buf:写缓冲区count:缓冲区大小offset:偏移量返回值: 成功:读到的字节数, 失败-1
      

    7. 将缓冲区数据写到磁盘
    在传统Unix系统实现中大多数磁盘I/O通过缓冲区进行的,当我们向文件写数据时,内核通常将数据复制到缓冲区中,之后再写到磁盘,这种方式称为延迟写。下面函数将缓冲区数据写入到磁盘。

     #include <unistd.h>void sync(void);将修改过的块缓冲区排队写到队列就返回,数据并不一定写入到磁盘。命令sync就是调用sync函数。update系统守护进程每30s调用一次该函数。int fsync(int fd);只对一个文件描述符其作用,并且磁盘操作结束后才返回。int fdatasync(int fd);等同于fsync,但是同时更新文件属性。
    

    8. 修改已打开的文件属性

     #include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );功能:修改已打开文件属性参数:fd:文件描述符cmd: F_DUPFD:复制文件描述符,新的文件描述符作为返回值返回。新文件描述符与旧fd共享同一文件表项,但是有自己的文件描述符标志,其FD_CLOEXXEC文件描述符标志被取消F_DUPFD_CLOEXEC:复制文件描述符,设置与新文件描述符关联的FD_CLOEXXEC文件描述符标志的值,返回新文件描述符F_GETFD:对应于fd的文件描述符标志作为函数返回值F_SETFD:对应fd设置文件描述符标志,新值为第三参数值F_GETFL:对应fd的文件状态标志作为函数返回值F_SETFL:将文件状态标志设置为第三个参数的值F_GETOWN:获取当前SIGIO和SIGURG信号的进程ID和组IDF_SETOWN:设置接收SIGIO和SIGURG信号的进程ID和组ID第三参数:总是一个整数,一般0返回值:出错:-1成功:其他
    

    9. ioctl 函数
    ioctl函数是I/O操作的万金油,内核对设备的IO通道控制操作函数,多用于驱动程序。

     #include <sys/ioctl.h>int ioctl(int fd, int request, ...);参数:@fd       :文件描述符的序号@request  :请求   代表不同操作的数字值@...      :可变参数,(写或者不写根据请求决定):传递的是整数,或者地址返回值:出错:-1成功:其他ioctl函数的实现需要一种命令码32位比特位     含义31 - 30   00 : 命令不带参数01 : 命令从驱动中获取数据,读方向10 : 命令把数据写入驱动,写方向11 : 命令即写又读:双向29 - 16      类型的大小15 - 8     类型7 - 0         序号
    

三 、标准I/O库

标志I/O库处理了很多细节,比如缓冲区的分配、优化块长度执行I/O等,更方便大家进行I/O操作
1. 流
在前面说的I/O函数都是围绕着文件描述符进行操作的,在标准I/O库里对应的是 流 进行操作的,当打开一个一个流时,标准I/O库函数fopen返回一个指向FILE对象的指针。它是一个结构体包含了标准I/O库
所管理该流的所有信息,包括用于实际I/O的文件描述符、指向用于该流的缓冲区指针、缓冲区长度、以及当前缓冲区中的字符等。
对应文件描述符每个进程定义了三个流,标准输入(stdin)、标准输出(stdout)、标准出错(stderr)

2.缓冲区
标准I/O库提供缓冲区的目的是为了尽可能减少使用read和write(太消耗资源了),它对每个I/O流自动地进行缓冲管理,库函数提供的接口,在内存中创建一块缓冲区,直到满足一定条件,才会真正写入,本质上还是系统调用,可以在不同系统间进行数据传输。
有以下三种缓冲
1.全缓冲,操作的文件,3个条件:

  1. 缓冲区满,则会刷新缓冲区 4096byte
  2. 程序正常结束
  3. fflush刷新缓冲区(将内容写到磁盘,在驱动程序表示丢弃缓冲区数据)

2.行缓冲:指针对终端进行操作,4个条件:

  1. 缓冲区满,则会刷新缓冲区 1024byte
  2. 程序正常结束
  3. fflush刷新缓冲区
  4. “\n”

3.无缓冲:指针终端进行操作

修改系统默认缓冲(一定要在流打开之后修改)

 #include <stdio.h>void setbuf(FILE *stream, char *buf);功能:打开或者关闭缓冲机制参数:stream:打开的流buf:指向一个长度为BUFSIZE的缓冲区,设置为null则关闭缓冲返回值:成功0,失败非0int setvbuf(FILE *stream, char *buf, int mode, size_t size);功能:打开或者关闭缓冲机制参数:stream:打开的流buf:指向一个长度为BUFSIZE的缓冲区,设置为null则系统自动分配mode:_IONBF :无缓冲,此选项可以忽略buf和size_IOLBF :行缓冲_IOFBF :全缓冲返回值:成功0,失败非0      刷新缓冲区,将所有未写的数据传输到内核。如果stream为null,则刷新所有缓冲区。#include <stdio.h>int fflush(FILE *stream);

3. 打开流
打开一个流默认是全缓冲,当打开终端设备时候默认为行缓冲。

 #include <stdio.h>FILE *fopen(const char *path, const char *mode);功能:打开一个标准I/O流参数:path:文件名mode:打开模式 (b:二进制文件)r/rb:打开文件对文件进行读操作,文件必须存在,r+/r+b/rb+:打开文件对文件进行读写操作,文件必须存在w/wb:打开或者创建文件,对文件进行写入w+/w+b/wb+:打开或者创建文件,对文件进行读写操作a/ab:打开或者创建文件,从文件末尾位置追加数据(多个进程追加一个文件也可以正确写入)a+/a+b/ab+:打开或者创建文件,从文件末尾进行读取、追加文件。如果文件不存在创建文件,从文件起始处读写。返回值:成功返回文件指针,失败返回nullFILE *fdopen(int fd, const char *mode);功能:取一个文件描述符,并使标准I/O流与之相关联,此函数常用于由创建管道和网络通信管道函数返回的描述符。因为这些特色文件不能用fopen打开。返回值:成功返回文件指针,失败返回nullFILE *freopen(const char *path, const char *mode, FILE *stream);功能:在一个指定流上打开一个文件,如果已经打开则先关闭再打开,此函数一般用于将一个文件打开为一个预定义的流:stdin、stdout、stderr参数:path:文件名mode:返回值:成功返回文件指针,失败返回null

4. 关闭流
当关闭一个流时候,缓冲区所有数据都被丢弃。

 #include <stdio.h>int fclose(FILE *fp);返回值:成功0,失败EOF(-1)

5. 读流和写流
每次打开一个I/O可以使用三种不同方式进程读写流
1. 每次读写一个字符的I/O
2. 每次读写一行的I/O,没次以换行符终止
3. 直接I/O,直接读写某种指定长度的对象,常用于二进制和结构体读写。

读写一个字符

 #include <stdio.h>int fgetc(FILE *stream);int getc(FILE *stream);int getchar(void);功能:读取数据参数:流返回值: 成功 读取的字符,失败 -1(EOF)区别:getc为宏。fgetc为函数,所以fgetc可以当做地址作为参数传递,getc不可以。#include <stdio.h>int fputc(int c, FILE *stream);int putc(int c, FILE *stream);int putchar(int c);功能:写入文件数据参数:c 写入的字符  stream 流返回值:成功 写入的字符,失败 EOF

6. 读写一行字符

    #include <stdio.h>char *fgets(char *s, int size, FILE *stream);char *gets(char *s);(不推荐使用,因为无法指定长度,可以造成缓冲区溢出)功能:读取文件中的一行字符,遇到\n 结束参数:s 指向用户开辟的缓冲区,实现定义一个数组size:要求读取字节个数stream:流返回值:成功 读取的字符串,失败 EOF;#include <stdio.h>int fputs(const char *s, FILE *stream);int puts(const char *s);功能:输出以null结尾的字符串数据数据到指定文件中参数:s 指定要被读取数据的缓冲区输出 \n 但不能输出\0返回值:成功 读取的字符串,失败 EOF;

7. 二进制I/O读写

 #include <stdio.h>size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);功能:读文件参数:ptr:事先定义的变量,需要传递变量的size:每个对象的大小number:对象个数stream:流返回值:成功:返回实际读取到对象的个数size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);功能:写文件参数:ptr:事先定义的变量,需要传递变量的size:每个对象的大小number:对象个数stream:流返回值:成功:返回实际写对象的个数注释:这两个函数存在一个问题就是只能读写同一系统上的数据,如果是不同系统则会造成问题。因为在不同系统同一结构的同一成员偏移量可能不同。

8. 定位流

 #include <stdio.h>int fseek(FILE *stream, long offset, int whence);功能:文件定位参数:stream 流offset:偏移量whence:基准点 SEEK_SET 文件开头位置SEEK_CUR 文件当前位置SEEK_END 文件末尾位置从后往前偏移加 - 号返回值:成功 0 失败 -1long ftell(FILE *stream);功能 返回当前文件位置指针的位置是在那个地址,使用数字的形式表示   参数:stream 流返回值:成功返回文件当前位置,出错-1.void rewind(FILE *stream);参数:stream 流功能: 把文件指针指向开头int fgetpos(FILE *stream, fpos_t *pos);功能:将文件位置指示器的当前值存入pos指向的对象中int fsetpos(FILE *stream, fpos_t *pos);功能:将文件位置定位到pos指示的值位置。

9. 格式化I/O

 格式化输出#include <stdio.h>int printf(const char *format, ...);功能:发送格式化输出到标准输出 stdout。参数:format -- 这是字符串,包含了要被写入到标准输出 stdout 的文本int fprintf(FILE *stream, const char *format, ...);功能:写入到指定的流。int sprintf(char *str, const char *format, ...);功能:将格式化字符串写入到str中,自动会加一个null字节参数:str:保存格式化的字符串format -- 这是字符串返回值:成功:返回写入到str中字符数(不包含null),失败负数int snprintf(char *str, size_t size, const char *format, ...);功能:同sprintf,但是sprintf可能会造成缓冲区溢出功能,所以snprintf会限定写入字节数。参数:str:保存格式化的字符串,自动会加一个null字节size:字符串大小format -- 这是字符串返回值:如果格式化后的字符串长度小于等于 size,则会把字符串全部复制到 str 中,并给其后添加一个字符串结束符 \0;如果格式化后的字符串长度大于 size,超过 size 的部分会被截断,只将其中的 (size-1) 个字符复制到 str 中,并给其后添加一个字符串结束符 \0,返回值为欲写入的字符串长度。失败:负数格式字符:  %h:输出short型%d 十进制有符号整数%md:m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。%ld:输出长整型数据。%lld: long long型%u 十进制无符号整数%f 浮点数  输出float%lf 浮点数  输出double%m.nf:输出共占m列,其中有n位小数,如数值宽度小于m左端补空格。 %-m.nf:输出共占m列,其中有n位小数,如数值宽度小于m右端补空格。%s 字符串%c 单个字符%p 指针的值%% 百分号本身%e 指数形式的浮点数%x, %X 无符号以十六进制表示的整数%o 无符号以八进制表示的整数%g(%G) 浮点数不显无意义的零"0"%p 输出地址符%lu 32位无符号整数%llu 64位无符号整数附加格式说明符m 输出数据域宽,数据长度<m,左补空格;否则按实际输出.n 对实数,指定小数点后位数(四舍五入)- 输出数据在域内左对齐(缺省右对齐)+  指定在有符号数的正数前显示正号(+)0    输出数值时指定左面不使用的空位置自动填0#   在八进制和十六进制数前显示前导0,0xl long类型输出 %lddouble类型输出 %lf格式化输入:#include <stdio.h>int scanf(const char *format, ...);功能:按照格式从终端输入数据参数:format:格式控制串%d 十进制整数%c 字符数据%s 字符串%f 浮点类型arg:可变参如果要将输入的数据保存在arg变量里面,需要传arg的地址返回值:成功:输入的个数 失败EOFint fscanf(FILE *stream, const char *format, ...);功能:从流 stream 读取格式化输入参数:stream :这是指向 FILE 对象的指针,该 FILE 对象标识了流。format :这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。int sscanf(const char *str, const char *format, ...);功能:从字符串读取格式化输入。参数:str:这是 C 字符串,是函数检索数据的源。format :这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。格式字符:同格式化输出

10. 临时文件

 #include <stdio.h>char *tmpnam(char *s);功能:产生一个与现有文件不同名的文件,每次调用都会产生不同路径的临时文件参数:保存返回的路径名返回值:返回文件路径名#include <stdio.h>FILE *tmpfile(void);功能:产生一个临时二进制文件(wb+),关闭该文件时会自动删除该文件参数:保存返回的路径名返回值:返回文件路径名

11 内存流
标准I/O库都是是将文件中数据取出来缓冲在内存中,现在我们可以直接通过缓冲区与主存直接来回传递数据,不依赖文件。仍然使用FILE指针,这些流看起来像文件流,其实是内存流。
内存流不访问文件只访问主存,所以如果标准I/O流作为参数用于临时文件的话,用内存流替代会有很大性能提高。

 #include <stdio.h>FILE *fmemopen(void *buf, size_t size, const char *mode);功能:内存流创建参数:buf:指向缓冲区的开始位置,如果为null,读写都没有任何意义。size:指定缓冲区大小的字节数,如果buf为null,则自动分配大小mode:同fopen的mode返回值:成功 返回流指针,失败nullFILE *open_memstream(char **ptr, size_t *sizeloc);功能:创建流面向字节#include <wchar.h>FILE *open_wmemstream(wchar_t **ptr, size_t *sizeloc);功能:创建流面向宽字节

四、高级I/O

非阻塞I/O、I/O多路转接、异步I/O、记录锁,这些都会在进程间通信用到

1. 非阻塞I/O
对于给定的文件描述符,有两种方法指定为非阻塞I/O。
1. 调用open获得描述符时候指定 O_NONBLOCK标志
2. 对于打开的文件描述符,调用fcntl函数,将O_NONBLOCK标志打开
2. 记录锁
记录锁:当一个进程正在读或者写一个文件某部分的时候,使用记录锁可以阻止其他进程修改同一文件区。

 int fcntl(int fd, int cmd, ... /* arg */ );对于记录锁,cmd的参数为 F_GETKL、F_SETLK、F_SETLKW。第三个参数为指向flock结构的指针struct flock {short l_type;      锁的类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)、F_UNLCK(解锁)short l_whence;     SEEK_CUR、SEEK_SET、SEEK_ENDoff_t l_start;        加锁或者解锁的区域起始偏移量off_t l_len;      区域长度pid_t l_pid;        持有锁阻塞当前的进程};如果len为0,表示锁的范围无限大,不管向文件追加多少数据都在锁范围内。对整个文件加锁,len=0,whence=EEK_SET。共享读锁:任意多个进程可以在给定字节上有一把共享读锁,独占性写锁:如果给定字节已经有写锁,那么不可再加任何锁。F_GETKL:判断由flock结构的指针所描述的锁是否会被另外一把锁排斥。如果存在一把锁,它阻止创建由flock结构的指针所描述的锁,如果不存在则吧type修改为F_UNLCKF_SETLK:由flock结构的指针所描述的锁,如果试图获取一把锁,系统阻止给我们锁则返回错误F_SETLKW:如果请求锁,因为其他进程在使用,则调用进程进入休眠,直到锁可用被唤醒。当一个进程终止时候,它所建立的所有锁都会释放,同样关闭一个文件描述符,与该文件描述符相关的锁都会释放。fork产生的子进程不继承父进程设置的锁。

3. I/O多路转接

  1. 对于从一个文件描述符读,然后又写另一个文件描述符这样的操作,我们通常这样写

         while(read(fd,buf,size)) {write(fd,buf,size);}
    

这种阻塞I/O操作,我们经常见,也是最低级的写法,因为可能因为读阻塞导致写阻塞。这时候我们使用异步I/O,进程告诉内核,当描述符准备好时候通过信号通知内核,但是他也有限制,只有在描述符是
网络或者终端设备时候才会起作用。

  1. IO多路复用基本思想

      先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回,函数返回时告诉进程已经有描述符就绪,可以进行IO操作。
    
  2. 实现函数select
    select函数可以使我们执行I/O多路转接,通过传给select函数的参数可以告诉内核:
    a.我们所关心的描述符
    b.对于每个描述符我们所关心的条件,是否想从一个给定描述符读/写,是否关心描述符异常
    c.愿意等待多长时间
    也可以通过返回值得到以下信息
    a.已经准备好的文件描述符
    b. 对于读、写、异常者三个条件中每一个,哪些已经准备好
    然后我们就可以使用read和write函数读写。

             #include<sys/time.h>#include<sys/types.h>#include<unistd.h>int select(int nfds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);参数:  nfds 所有监控文件描述符最大的那一个 +1.(因为文件描述符编号从0开始,所以要加1)read_fds 所有可读的文件描述符集合。     没有则为NULLwrite_fds 所有可写的文件描述符集合。     没有则为NULLexcept_fds 处于异常条件的文件描述符     没有则为NULLtimeval: 超时设置。 NULL:一直阻塞,直到有文件描述符就绪或出错0   :仅仅监测文件描述符集的状态,然后立即返回非0 :在指定时间内,如果没有事件发生,则超时返回返回值:当timeval设置为NULL:返回值 -1 表示出错>0 表示集合中有多少个描述符准备好当设置timeval非0时: 返回值 -1:表示出错>0: 表示集合中有多少描述符准备好=0: 表示时间到了还没有描述符准备好对于fd_set数据类型有以下四种处理方式    fd:文件描述符、 fdset文件描述符集合void FD_SET(int fd,fd_set *fdset):   将fd加入到fdest        void FD_CLR(int fd,fd_set *fdest):  将fd从fdest里面清除void FD_ZERO(fd_set *fdest):        从fdest中清除所有文件描述符void FD_ISSET(int fd,fd_set *fdest):判断fd是否在fdest集合中这些接口实现为宏或者函数,调用  FD_ZERO 将fd_set变量的所有位置设置为0,如果要开启描述符集合的某一位,可以调用 FD_SET ,调用FD_CLR 可以清除某一位,FD_ISSET用来检测某一位是否打开。在申明了一个描述符集合之后,必须使用FD_ZERO将其清零,下面是使用操作:fd_set reset;int fd;FD_ZERO(&reset);FD_SET(fd, &reset);FD_ZERO(STDIN_FILENO, &reset);if (FD_ISSET(fd, &reset)) {}对于“准备好” 这个词这里说明一下,什么才是准备好,什么是没有准备好,如果对读集(read_fds/write_fds) 中的一个描述符进行read/write操作没有阻塞则认为是准备好,或者对except_fds有一个未决异常条件,则认为准备好。一个描述符的阻塞并不影响整个select的阻塞。当文件描述符读到文件结尾时候,read返回0.
    
  3. 实现函数poll
    poll函数与select函数相似,不同的是,poll不是为每个条件(读、写、异常)构造一个文件描述符,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号,poll函数可以用于任何类型的文件描述符。

      #include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数:fds:pollfd结构数组struct pollfd {int   fd;         /* 文件描述符 */short events;     /* 请求事件 */short revents;    /* 返回事件 */};events:需要将events设置为以下一个或者多个值,这些值会告诉内核哪些是我们关系的文件描述符POLLIN       不阻塞地读高优先级数据意外的数据POLLRDNORM  不阻塞地读普通数据POLLRDBAND 不阻塞地读优先级数据POLLPRI       不阻塞地读高优先级数据POLLOUT      普不阻塞地读写普通数据POLLWRNORM   同POLLOUTPOLLWRBAND  不阻塞地写低优先级数据POLLERR      发生错误POLLHUP     发生挂起(当挂起后就不可以再写该描述符,但是可以读)POLLNVAL 描述字不是一个打开的文件revents:返回的文件描述符,用于说明描述符发生了哪些事件。nfds:数组中元素数timeout:等待时间= -1:永远等待,直到有一个描述符准备好,或者捕捉到一个信号,如果捕捉到信号返回-1。= 0 :不等待,立即返回。这是轮询的方法。> 0: 等待的毫秒数,有文件描述符准备好或者timeout超时立即返回。超时返回值为0.
    
  4. 散布读和聚集写
    就是在一次函数调用中读、写多个非连续的缓冲区。

     #include <sys/uio.h>ssize_t readv(int fd, const struct iovec *iov, int iovcnt);功能:散布读参数:fd:文件描述符iov:iovec结构指针struct iovec {void *iov_base;   缓冲地址size_t iov_len;     缓冲大小};iovcnt:iov数组元素个数返回值:成功:已读个数,失败:-1ssize_t writev(int fd, const struct iovec *iov, int iovcnt);功能:聚集写参数:fd:文件描述符iov:iovec结构指针struct iovec {void *iov_base;    缓冲地址size_t iov_len;     缓冲大小};iovcnt:iov数组元素个数返回值:成功:已写个数,失败:-1
    
    1. 存储映射I/O
      存储映射I/O,将一个磁盘文件映射到内存中的一个缓冲区上,从这个缓冲区读写数据就相当于读写文件数据,就可以不再使用read、write。

       #include <sys/mman.h>void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t offset);功能:将文件或设备空间映射到共享内存区,因此当从共享内存读数据时就相当于从文件中读取数据参数:  addr:要映射的起始地址,通常为NULL,让内核自动分配len:映射到进程地址空间的字节数port:映射区保护方式   PROT_READ   映射区可读PROT_WRITE 映射区可写PROC_EXEC  映射区可执行PROC_NONE 映射区不可访问 flags:           MAP_SHARED  变动是共享的MAP_PRIVATE   变动是私有的MAP_FIXED 准确解释addr参数, 如果不指定该参数, 则会以4K大小的内存进行对齐MAP_ANONYMOUS   建立匿名映射区, 不涉及文件fd:  文件描述符,使用前必须先打开文件。offset:从文件头开始偏移量为0 p=(STU*)mmap(NULL,sizeof(STU)*5,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0)      //STU*  数据类型
      

五、终端I/O

终端I/O系统是一个非常复杂的东西,我们不会去讲解它,但是它有几个非常重要的函数需要我们学,这几个函数用来去嵌入式的串口编程

1. 获取/设置终端参数

    #include <termios.h>#include <unistd.h>int tcgetattr(int fd, struct termios *termios_p);功能:获取终端属性参数:fd:打开串口设备节点描述符termios_p:终端属性结构体指针int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);功能:设置终端属性参数:fd:打开串口设备节点描述符termios_p:终端属性结构体指针:有70多种标志(这里不详细介绍,后面会说)optional_actions:TCSANOW: 更改立即发生TCSADRAIIN: 发送所有输出后更改才发生,更改输出参数选用这个TCSAFLUSH: 发送所有输出后更改才发生,更改时所有未读数据全部丢弃

2. 波特率

 #include <termios.h>#include <unistd.h> speed_t cfgetispeed(const struct termios *termios_p);speed_t cfgetospeed(const struct termios *termios_p);int cfsetispeed(struct termios *termios_p, speed_t speed);int cfsetospeed(struct termios *termios_p, speed_t speed);功能:获取/设置波特率参数:termios_p:struct termios结构体指针speed:波特率:B50、B75、B110、B150、B200、B300、B600、B1200、B1800、B2400、B4800、B9600、B19200、B38400、B57600、B115200返回值:成功0 失败-1.在调用cfget函数之前先调用tcgetattr函数获取struct termios结构指针

3. 控制函数

 int tcflush(int fd, int queue_selector);功能:冲洗缓冲区参数:fd:打开串口设备节点描述符queue_selector:TCIFLUSH:冲洗输入队列TCOFLUSH:冲洗输出队列TCIOFLUSH:冲洗输入和输出缓冲队列返回值:成功0 失败-1int tcsendbreak(int fd, int duration);功能:指定时间区间内发送连续的0值位流参数:fd:打开串口设备节点描述符duration: 0:传递延续0.25-0.5s非0:传递时间依赖于实现返回值:成功0 失败-1int tcdrain(int fd);功能:等待所以输出都被传递返回值:成功0 失败-1int tcflow(int fd, int action);功能:对输入输出流进行控制参数:action:TCOOFF:输出被挂起TCOON:启动被挂起的输出TCIOOFF:发送一个stop,终端设备停止发送数据TCION: 发送一个START,终端设备继续发送数据返回值:成功0 失败-1

4. Linux系统下串口编程实现demo

 #include <stdio.h>#include <pthread.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <termios.h>#include <string.h>#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))#define MAX_BAUD 115200#define UART_IDX "/dev/ttyUSB0"#define CRC_OK    0#define CRC_FAIL -1typedef enum {STANDARD_INPUT_MODE = 1,RAWDATA_MODE} uart_mode_e;static int fd;static pthread_t read_thread_id;static int usb_thread_run;static char recv_buf[1024 * 100];static unsigned char recvmsg[1024 * 100];static int ws_uart_send(char *buf, int len){unsigned int total_byte = 0;int send_byte;while (len > 0) {if (len < 1024)send_byte = write(fd, buf+total_byte, len);elsesend_byte = write(fd, buf+total_byte, 1024);if (send_byte < 0) {tcflush(fd, TCOFLUSH);printf("data send error\n");return -1;}len -= send_byte;total_byte += send_byte;printf("len = %d total_byte = %d\n", len, total_byte);}return 0;}static void *read_thread(void *arg){int count,ret = 0;int cnt;int total;char buf[64] = {0};usb_thread_run = 1;while (usb_thread_run) {memset(recv_buf, 0, sizeof(recv_buf));cnt = 0;total = 0;count = read(fd, buf, 64);printf("cnt = %d\n", count);printf("buf = %s\n", buf);write(fd, buf, count);    }return NULL;}/*** @brief  * @note   * @param  fd: * @param  baud_rate: * @retval */int set_uart_baud_rate(int fd, int baud_rate){int ret = 0;struct termios param;int speed_arr[] = { B921600, B576000, B500000, B460800, B230400,B115200, B38400, B19200, B9600, B4800, B2400, B1200 };int name_arr[] = { 921600, 576000, 500000, 460800, 230400,115200, 38400, 19200, 9600, 4800, 2400, 1200 };int i = 0;int status;status = tcgetattr(fd, &param);if(0 != status){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}for(i = 0; i < ARRAY_SIZE(speed_arr); i++){if(baud_rate == name_arr[i]){tcflush(fd, TCIOFLUSH);if(0 != cfsetispeed(&param, speed_arr[i])){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}if(0 != cfsetospeed(&param, speed_arr[i])){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}status = tcsetattr(fd, TCSANOW, &param);if(0 != status){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}tcflush(fd, TCIOFLUSH);break;}}if(i == ARRAY_SIZE(speed_arr)){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}return ret;}/*** @brief  * @note   * @param  fd: * @param  databits: * @param  stopbits: * @param  parity: * @param  mode: * @retval */int set_uart_parity(int fd, int databits, int stopbits, int parity, uart_mode_e mode){int ret = 0;struct termios param;if(tcgetattr(fd, &param) != 0){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}param.c_cflag &= ~CSIZE;switch(databits) /*设置数据位数*/{case 7:param.c_cflag |= CS7;break;case 8:param.c_cflag |= CS8;break;default:return ret;}switch(parity){case 'n':case 'N':param.c_cflag &= ~PARENB;           /* Clear parity enable */param.c_iflag &= ~(INPCK | ICRNL | IXON);            /* Enable parity checking */break;case 'o':case 'O':param.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/param.c_iflag |= INPCK;             /* Disnable parity checking */break;case 'e':case 'E':param.c_cflag |= PARENB;            /* Enable parity */param.c_cflag &= ~PARODD;           /* 转换为偶效验*/param.c_iflag |= INPCK;             /* Disnable parity checking */break;case 'S':case 's':                               /*as no parity*/param.c_cflag &= ~PARENB;param.c_cflag &= ~CSTOPB;break;default:return ret;}/* 设置停止位*/switch(stopbits){case 1:param.c_cflag &= ~CSTOPB;break;case 2:param.c_cflag |= CSTOPB;break;default:return ret;}if(mode == STANDARD_INPUT_MODE)/*标准输入设置*/{param.c_lflag &= ~(ECHO); //关闭回显param.c_lflag |= (ICANON);param.c_oflag |= OPOST;   //}/*raw data mode*/else if(mode == RAWDATA_MODE){param.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);param.c_oflag &= ~OPOST;  //raw output}/* Set input parity option */if(parity != 'n'){param.c_iflag |= INPCK;}param.c_cc[VTIME] = 10; //10 // 1 secondsparam.c_cc[VMIN] = 0;tcflush(fd, TCIFLUSH);/* Update the options and do it NOW */if(tcsetattr(fd, TCSANOW, &param) != 0){printf("%s:%d error = %d\n",__func__, __LINE__, ret);return ret;}return ret;}void uart_recv_start(void){printf("recv start");pthread_create(&read_thread_id, NULL, read_thread, NULL);pthread_detach(read_thread_id); }void uart_deinit(){usb_thread_run = 0;close(fd);pthread_join(read_thread_id, NULL);}int main(int argc, char* argv[]){struct termios oldtio, newtio;int ret = 0;char buf[256];fd = open(argv[1], O_RDWR | O_NOCTTY);if (fd < 0) {printf("Open %s failed\n", argv[1]);return -1;} elseprintf("Open %s successfully\n", argv[1]);set_uart_baud_rate(fd, 115200);ret = set_uart_parity(fd, 8, 1, 'n', RAWDATA_MODE);uart_recv_start();while (1) {sleep(1);}}

Linux系统下I/O操作讲解,深入了解实战高级I/O编程相关推荐

  1. Linux系统下文件与目录操作讲解

    Linux系统下文件与目录操作 在Linux系统中有一句话叫做,一切皆文件.上节我们说的I/O操作,对于普通I/O操作,都是打开.读写.关闭.这节我们通过stat结构体来了解文件的一些基本信息和本质特 ...

  2. 在linux操作系统中启动oracle数据库程序,Linux系统下Oracle数据库的安装和启动关闭操作教程...

    这篇文章主要介绍了Linux系统下Oracle数据库的安装和启动关闭操作教程,并针对在sqlplus下所需执行的命令进行讲解,需要的朋友可以参考下 1.准备: df -H 查看空间剩余   一般准备最 ...

  3. linux 文件目录操作,Linux系统下文件与目录操作

    与其它操作系统一样,在Linux系统下用户的数据和程序也是以文件的形式保存的.所以在使用Linux的过程中,是经常要对文件与目录进行操作的.现在我们就以一个Window用户的立场来学习一下Linux下 ...

  4. Linux系统下授权MySQL账户访问指定数据库和数据库操作

    Linux系统下授权MySQL账户访问指定数据库 需求: 1.在MySQL中创建数据库mydata 2.新建MySQL账户admin密码123456 3.赋予账户admin对数据库mydata具有完全 ...

  5. Linux系统下文件与目录操作

    Linux系统下文件与目录操作  与其它操作系统一样,在Linux系统下用户的数据和程序也是以文件的形式保存的.所以在使用Linux的过程中,是经常要对文件与目录进行操作的.现在我们就以一个Windo ...

  6. Linux系统下操作的常用快捷键

    Linux系统下操作的常用快捷键     Ubuntu基本快捷键(可能有重复.不全)欢迎指正 * 打开主菜单 = Alt + F1 类似Windows下的Win键,在GNOME中打开"应用程 ...

  7. Linux系统下操作Oracle数据库

    Linux系统下操作Oracle数据库 Oracle Database,又名Oracle RDBMS,或简称Oracle.是甲骨文公司的一款关系数据库管理系统.它是在数据库领域一直处于领先地位的产品. ...

  8. linux系统下grub.cfg详解和实例操作

    linux系统下grub.cfg详解和实例操作 简介 grub是引导操作系统的程序,它会根据自己的配置文件,去引导内核,当内核被加载到内存以后,内核会根据grub配置文件中的配置,找到根分区所使用的文 ...

  9. Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配

    Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件 Unix/Linux操作系统分析实验四 设备驱动: ...

最新文章

  1. 刚火了的中台转头就拆,一大波公司放不下又拿不起来!
  2. C++中关键字的理解--Static
  3. 两概率分布交叉熵的最小值是多少?
  4. python定义类object_Python之ClassObject用法详解
  5. scrapy 去重策略修改
  6. java获取不到ipv6的网卡
  7. matlab索引超出数组边界且不提示数组边界的一种处理办法
  8. 【CF55D】Beautiful Numbers-数位DP+优化
  9. cas5.2集成ldap
  10. erraddsave.php,DedeCms教程:挑错管理
  11. 解决VirtualBox配置静态ip和连外网的问题
  12. 基于Java的大型网站设计方案
  13. 易企秀怎么转换成html5,易企秀怎么免费制作h5?
  14. 用pygame做一个简单的python小游戏---生命游戏
  15. linux 冒号用法
  16. LabVIEW以编程方式启用IEPE激励
  17. 微信小程序开发 | 02 - 轮播图实现(swiper组件)
  18. 涨停缩量平台调整选股策略(附筛选python代码)
  19. ubuntu 安装 LiveSuit 刷机工具
  20. js 写html代码编辑器,原生JS写一个功能强大的编辑器

热门文章

  1. Jackson安全漏洞版本升级
  2. python脚本批量登录crt_Python实现批量新建SecureCRT Session
  3. php获取微信图片访问权限,微信小程序访问图片出现403,图片防盗链的解决办法...
  4. php商品评价代码,php商品对比功能代码分享
  5. linux svn上传目录_Linux系统下svn更新自动同步到web目录
  6. unity ab包加载_Unity资源管理和打包
  7. mysql判断是否包含某个字符的方法
  8. redhad6.4 配置在线yum源
  9. mysqld_exporter报错Error 1146: Table 'my2.status' doesn't exist
  10. android 电话拨号器