系统调用IO和标准IO
2019/09/20 13:38:50 来源:Linux社区 作者:songhe364826110

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中选择指定一个,还可以根据需要或上以下常值:

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操作从文件的当前偏移量处开始,在成功返回之前,文件偏移量将增加实际读到的字节数。
有几种情况可能导致实际读到的字节数少于要求读的字节数:

write

write用于向文件写入数据。

#include <unistd.h>

//成功返回写入的字节数,失败返回-1
ssize_t write(int fd, const void *buf, size_t count);

lseek

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

#include <sys/types.h>
#include <unistd.h>

//成功返回新的文件偏移量,失败返回-1
off_t lseek(int fd, off_t offset, int whence);

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

注意:

ioctl

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

#include <sys/ioctl.h>

//出错返回-1,成功返回其他值
int ioctl(int fd, int cmd, ...);

2. 标准IO(带缓冲IO)

概述

标准IO其实就是stdio.h头文件中提供的IO接口,只不过在特定的系统中可能有特定的内部实现。
和系统调用IO类似,标准IO也预定义了三个文件指针stdin、stdout、stderr,分别对应标准输入、标准输出、标准错误。

缓冲与冲洗

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

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

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

//成功返回0,失败返回非0
void setbuf(FILE *fp, char *buf);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);

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

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

常用标准IO函数

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

打开和关闭流

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

//成功返回0,失败返回EOF
void fclose(FILE *fp);

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

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

定位流

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

//成功返当前文件位置,出错返回-1
int ftell(FILE *fp);

//成功返回0,失败返回-1
void fseek(FILE *fp, long offset, int whence);

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

文本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, ...);
//成功返回输入的字符数,到达文件尾或失败返回EOF
int scanf(const char *format, ...);
int sscanf(const char *buf, const char *format, ...);

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

Linux公社的RSS地址https://www.linuxidc.com/rssFeed.aspx

本文永久更新链接地址https://www.linuxidc.com/Linux/2019-09/160746.htm


6

本栏最新