1. 系统调用IO(无缓冲IO)

系统调用

在Linux中一切皆文件,文件操作在Linux中是十分重要的。为此, Linux内核提供了一组用户进程与内核进行交互的接口用于对文件和设备进行访问控制,这些接口被称为系统调用。

系统调用对于应用程序最大的作用在于:

  • 以统一的形式,为应用程序提供了一组文件访问的抽象接口,应用程序不需要关心文件的具体类型,也不用关心其内部实现细节。

常用系统调用IO函数

常用的系统调用IO函数有5个:open、close、read、write、ioctl,此外还有个非系统调用IO函数lseek,系统调用IO都是不带缓冲的IO。

open

open用于创建一个新文件或打开一个已有文件,返回一个非负的文件描述符fd。

0、1、2为系统预定义的文件描述符,分别代表STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //成功返回文件描述符,失败返回-1
int open(const char *pathname, int flags, ... /* mode_t mode */);

flags参数一般在O_RDONLY、O_WRONLY和O_RDWR中选择指定一个,还可以根据需要或上以下常值:

  • O_CREAT:若文件不存在则创建它,此时需要第三个参数mode,mode可设的值及含义如下图所示。
  • O_APPEND:每次写时都追加到文件的尾端
  • O_NONBLOCK:如果pathname对应的是FIFO、块特殊文件或字符特殊文件,则该命令使open操作及后续IO操作设定为非阻塞模式

close

close用于关闭一个已打开文件。

#include <unistd.h>

//成功返回0,失败返回-1
int close(int fd);

进程终止时,内核会自动关闭它所有的打开文件,应用程序经常利用这一点而不显式关闭文件。

read

read用于从打开文件中读数据。

#include <unistd.h>

//成功返回读到的字节数;若读到文件尾则返回0;失败返回-1
ssize_t read(int fd, void *buf, size_t count);

read操作从文件的当前偏移量处开始,在成功返回之前,文件偏移量将增加实际读到的字节数。

有几种情况可能导致实际读到的字节数少于要求读的字节数:

  • 读普通文件时,在读到要求字节数之前就到达了文件尾。例如,离文件尾还有30字节,要求读100字节,则read返回30,下次在调用read时会直接返回0
  • 从网络读时,网络中的缓冲机制可能造成返回值少于要求读的字节数,解决办法在网络编程专题中再讲

write

write用于向文件写入数据。

#include <unistd.h>

//成功返回写入的字节数,失败返回-1
ssize_t write(int fd, const void *buf, size_t count);
  • write的返回值通常与参数count相同,否则表示出错。
  • 对于普通文件,write操作从文件的当前偏移量处开始
  • 若指定了O_APPEND选项,则每次写之前先将文件偏移量设置到文件尾
  • 成功写入之后,文件偏移量增加实际写的字节数。

lseek

lseek用于设置打开文件的偏移量。

#include <sys/types.h>
#include <unistd.h> //成功返回新的文件偏移量,失败返回-1
off_t lseek(int fd, off_t offset, int whence);

对offset的解释取决于whence的值:

  • 若whence == SEEK_SET,则将文件偏移量设为距文件开头offset个字节,此时offset必须为非负
  • 若whence == SEEK_CUR,则将文件偏移量设为当前值 + offset,此时offset可正可负
  • 若whence == SEEK_END,则将文件偏移量设为文件长度 + offset,此时offset可正可负

注意:

  • lseek仅将新的文件偏移量记录在内核中,它并不引起任何IO操作,因此它不是系统调用IO,但该偏移量会用于下一次read/write操作
  • 管道、FIFO和套接字不支持设置文件偏移量,不能对其调用lseek

ioctl

ioctl提供了一个用于控制设备及其描述符行为和配置底层服务的接口。

#include <sys/ioctl.h>

//出错返回-1,成功返回其他值
int ioctl(int fd, int cmd, ...);
  • ioctl对描述符fd引用的对象执行由cmd参数指定的操作
  • 每个设备驱动程序都可以定义它自己专用的一组ioctl命令

2. 标准IO(带缓冲IO)

概述

标准IO其实就是stdio.h头文件中提供的IO接口,只不过在特定的系统中可能有特定的内部实现。

和系统调用IO类似,标准IO也预定义了三个文件指针stdin、stdout、stderr,分别对应标准输入、标准输出、标准错误。

缓冲与冲洗

标准IO是带缓冲的IO,一共有3种类型的缓冲:

  • 全缓冲:缓冲区填满后才进行IO操作,如磁盘文件
  • 行缓冲:遇到换行符才进行IO操作,如命令行终端(stdin和stdout)
  • 无缓冲:不经过缓冲,立即进行IO操作,如stderr

一般情况下,系统默认使用下列类型的缓冲:

  • stderr是无缓冲的
  • 指向终端设备的流是行缓冲的,否则是全缓冲的

对于一个打开的流,可以调用setbuf或setvbuf改变其缓冲类型.

//成功返回0,失败返回非0
void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);
  • setbuf用于关闭或打开fp的缓冲机制,若打开,则buf必须指向一个长度为BUFSIZ的缓冲区;若关闭,则buf设为NULL
  • setvbuf通过参数mode可精确设置缓冲类型,_IOLBF==全缓冲, _IOLBF==行缓冲,_IONBF==无缓冲
  • 当setvbuf设置为带缓冲时,buf必须指向一个长度为size的缓冲区,推荐将buf设为NULL让系统自动分配缓冲区长度,此时size可设为0

对于全缓冲和行缓冲,不管是否满足IO条件,都可以使用fflush函数强制进行IO操作,我们称其为冲洗。

//成功返回0,失败返回EOF
//若fp为NULL,将导致冲洗所有输出流
int fflush(FILE *fp);

常用标准IO函数

常用的标准IO函数分为以下几大类:

  • 打开和关闭流
  • 定位流
  • 读写流,包括文本IO、二进制IO和格式化IO三种

打开和关闭流

//成功返回文件指针,失败返回NULL
FILE *fopen(const char *pathname, const char *type); //成功返回0,失败返回EOF
void fclose(FILE *fp);

fopen打开由pathname指定的文件,type指定读写方式:

  • 使用字符b作为type的一部分,使得标准IO可以区分文本文件和二进制文件
  • Linux内核不区分文本和二进制文件,因此在Linux系统下使用字符b实际上没有作用,只读、只写、读写分别指定为"r"、"w"、"rw"即可
  • Windows中,文本文件只读、只写、读写分别为"r"、"w"、"rw",二进制文件只读、只写、读写分别为"rb"、"wb"、"rb+"
  • 只读方式要求文件必须存在,只写或读写方式会在文件不存在时创建

fclose关闭文件,关闭前缓冲区中的输出数据会被冲洗,输出数据则丢弃。

定位流

流的定位类似于系统调用IO中获取当前文件偏移量,ftell和fseek函数可用于定位流。

//成功返当前文件位置,出错返回-1
int ftell(FILE *fp); //成功返回0,失败返回-1
void fseek(FILE *fp, long offset, int whence);

offset和whence含义及可设的值与系统调用IO中的lseek相同,不再赘述,但如果是在非Linux系统,则有一点需要注意:

  • 对于二进制文件,文件位置严格按照字节偏移量计算,但对于文本文件可能并非如此
  • 定位文本文件时,whence必须是SEEK_SET,且offset只能是0或ftell返回值

文本IO

文本IO有两种:

  • 每次读写一个字符
  • 每次读写一行字符串
/*
* 每次读写一个字符
*/ //成功返回下一个字符,到达文件尾或失败返回EOF
int getc(FILE *fp); //可能实现为宏,因此不允许将其地址作为参数传给另一个函数,因为宏没有地址
int fgetc(FILE *fp); //一定是函数
int getchar(); //等价于getc(stdin) //成功返回c,失败返回EOF
int putc(int c, FILE *fp); //可能实现为宏,因此不允许将其地址作为参数传给另一个函数,因为宏没有地址
int fputc(int c, FILE *fp); //一定是函数
int putchar(int c); //等价于putc(c, stdout)
/*
* 每次读写一行字符串
*/ //成功返回str,到达文件尾或失败返回EOF
char *fgets(char *str, int n, FILE *fp); //从fp读取直到换行符(换行符也读入),str必须以'\0'结尾,故包括换行符在内不能超过n-1个字符 //成功返回非负值,失败返回EOF
int fputs(const char *str, FILE *fp); //将字符串str输出到fp,str只要求以'\0'结尾,不一定含有换行符

二进制IO

二进制IO就是fread和fwrite。

//返回读或写的对象数
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);

二进制IO常见的用法包括:

  • 读写一个二进制数组
  • 读写一个结构

上述两种用法结合起来,还可以实现读写一个结构数组。

struct Item
{
int id;
char text[100];
}; int data[10];
struct Item item;
struct Item item[10]; //读写二进制数组
fread(&data[2], sizeof(int), 4, fp);
fwrite(&data[2], sizeof(int), 4, fp); //读写结构
fread(&item, sizeof(item), 1, fp);
fwrite(&item, sizeof(item), 1, fp); //读写结构数组
fread(&item, sizeof(item[0]), sizeof(item) / sizeof(item[0]), fp);
fwrite(&item, sizeof(item[0]), sizeof(item) / sizeof(item[0]), fp);

格式化IO

格式化IO包括输入函数族和输出函数族,我们剔除了不常用的与文件指针fp、文件描述符fd相关的API,仅保留常用的3个输出函数和2个输入函数。

//成功返回输出或存入buf的字符数(不含'\0'),失败返回负值
int printf(const char *format, ...);
int sprintf(char *buf, const char *format, ...);
int snprintf(char *buf, size_t n, const char *format, ...);
  • sprintf和snprintf会自动在buf末尾添加字符串结束符'\0',但该字符不包括在返回值中
  • snprintf要求调用者保证缓冲区buf长度n足够大
//成功返回输入的字符数,到达文件尾或失败返回EOF
int scanf(const char *format, ...);
int sscanf(const char *buf, const char *format, ...);

PS:sscanf在实际工程中有一个实用的小技巧:串口接收的一条报文,可以根据串口协议,使用sscanf提取各个字段,从而快速便捷的进行报文解析。

系统调用IO和标准IO的更多相关文章

  1. 文件IO和标准IO

    2015.2.26 星期四,阴天 今天的内容主要是文件IO man 手册的分册: man -f open 查看那些分册中有openman 1 -- 普通的命令程序man 2 -- 系统调用man 3 ...

  2. 文件IO与标准IO的区别

    文件IO与标准IO的区别 文件I/O就是操作系统封装了一系列函数接口供应用程序使用,通过这些接口可以实现对文件的读写操作,文件I/O是采用系统直接调用的方式,因此当使用这些接口对文件进行操作时,就会立 ...

  3. 文件IO和标准IO的区别【转】

    一.先来了解下什么是文件I/O和标准I/O: 文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O).不带缓存指的是每个read,write都调用内核中的一个系统调用.也就是一般所 ...

  4. 8 . IO类-标准IO、文件IO、stringIO

    8.1 IO类 #include <iostream> //标准IO头文件  8.2 文件输入输出流 #include <fstream> //读写文件头文件 std::fst ...

  5. linux文件io与标准io

    文件IO实际是API,Linux对文件操作主要流程为:打开(open),操作(write.read.lseek),关闭(close). 1.打开文件函数open(): 涉及的头文件:  #includ ...

  6. 文件的概念、标准IO其一

    1.文件的概念 文件是一种存储在磁盘(掉电不丢失存储设备)上,掉电不丢失的一种存储数据的方式,文件在系统中有以下层次的结构来实现. 系统调用.文件IO.标准IO的关系如下: 2.linux系统的文件分 ...

  7. (九)errno和perror、标准IO

    3.1.6.文件读写的一些细节3.1.6.1.errno和perror(1)errno就是error number,意思就是错误号码.linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会 ...

  8. 【linux草鞋应用编程系列】_1_ 开篇_系统调用IO接口与标准IO接口

    最近学习linux系统下的应用编程,参考书籍是那本称为神书的<Unix环境高级编程>,个人感觉神书不是写给草鞋看的,而是 写给大神看的,如果没有一定的基础那么看这本书可能会感到有些头重脚轻 ...

  9. 二、linux IO 编程---系统调用和POSIX标准和标准IO

    2.1 系统调用 2.1.1 概念 所谓系统调用(system call)是指曹错系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务. 应用程序可以 ...

随机推荐

  1. springboot 整合rabbitMQ

    RabbitMQ RabbitMQ是一个被广泛使用的开源消息队列.它是轻量级且易于部署的,它能支持多种消息协议.RabbitMQ可以部署在分布式和联合配置中,以满足高规模.高可用性的需求. 在pom. ...

  2. [LeetCode] 249. Group Shifted Strings 分组偏移字符串

    Given a string, we can "shift" each of its letter to its successive letter, for example: & ...

  3. 大网扫描在发现APT组织方面的一个应用

    如何发现CobalStike服务端? 答: 扫HTTP response header: "HTTP/1.1 404 Not Found" balaba-"Server& ...

  4. MAVEN安装代码到本地库,安装jar, source, javadoc的方式

    cd /d %~dp0 mvn -Dpackaging=jar mvn -Dpackaging=jar -Dclassifier=sources mvn -Dpackaging=jar -Dclass ...

  5. 「模拟赛20191019」C 推式子+贪心+树状数组

    题目描述 给定一棵\(n\)个点的有根树,根节点编号为\(1\),点有点权. 定义\(d(v)\)表示\(v\)到\(1\)的路径上的边数. 定义\(f(v,u)\)在\(v<u\)且\(v\) ...

  6. SpringBoot整合AbstractRoutingDataSource实现读写分离

    在配置数据源时候,已经把主库和从库的数据源配置到DynamicDataSource里了 利用AbstractRoutingDataSource实现动态切换数据源,可以通过注解或者根据方法名前缀切换要使 ...

  7. elementui禁用全选按钮

     document.getElementsByClassName('el-checkbox__input')[0].classList.add('is-disabled')           doc ...

  8. 使用PHP开发HR系统(6)

        本节讲述如何连接Postgre数据库并查询与显示数据. ==================================================================== ...

  9. jwt 0.9.0(二)jwt官网资料总结

    1.JWT描述 Jwt token由Header.Payload.Signature三部分组成,这三部分之间以小数点”.”连接,JWT token长这样: eyJhbGciOiJIUzI1NiIsIn ...

  10. wait(),notify(),notifyAll()必须加锁的原因

    从语义方面解析为什么需要锁: 1.wait()方法会释放锁,如果没有先获得锁,那么如何释放? 从实际的作用: 为了预防饥饿线程的产生. 原因: // 线程A 的代码 while(!condition) ...