Linux 系统编程导读 - 缓冲输入输出

简介

本章主要介绍C所提供的标准I/O操作。不同于前面的open只能在Linux上被支持,C标准接口能够在不同系统中被支持。两个基本的概念,关于底层的I/O操作,效率问题。

  1. 磁盘,读写最下层就是磁盘的操作。所有的磁盘操作都是基于块进行,这句话的意思就是如果跨越块进行读写会导致效率降低,因此读写请求若选择块大小的整数倍和约数倍的话,就能够保证不会出现跨越块进行读写的问题。
  2. 内存,常听到的内存对齐问题。CPU进行读写数的时候,对于32位的机器,一次读取写入4字节,并且是按照起始地址开始的,例如CPU要读取一个值时,它只会从4的倍数的地址进行读写,只会读取0,4,8这些地址(起始地址为0)。因此内存对齐的好处是,假设我们要读一个4字节的值,地址在0x5,那么CPU就要先读0x4,取出3字节;然后再读0x8,取出1字节,相当于进行了两次操作,严重影响效率;对齐的话,就能够保证不会产生多余的操作。

而C的接口默认优化了这些问题,内存对齐一般编译器会优化好。
** 其他 **:
分区对齐:将分区的起始位置放在扇区的0处,这样才能保证每一个block都在单独的一个扇区中,一个扇区中包含多个block,若不对齐,那么将会导致一个block跨越两个扇区,严重影响速度。

用户缓冲I/O

用户缓冲I/O的作用类似于内核的缓冲区,在用户空间设置输入输出的缓冲区,目的也是为了提供I/O效率。
标准I/O不同于系统I/O,它使用的不是文件描述符操作,而是文件指针,也就是”流”,最常见的是标准输入输出流,stdin和stdout,对应于文件描述符0和1。

打开文件

返回的是文件指针,也就是 “流”,下面的接口也是最常用:

1
2
#include <stdio.h>
FILE* fopen(const char * path, const char * mode);

因为文件描述符和流是对应的,因此也可以由文件描述符转化为 流:
1
2
#include <stdio.h>
FILE * fdopen (int fd, const char *mode);

两者将会关联,通过一个修改就会影响另一个的操作。模式必须匹配。

关闭流

最常用的就是fclose了:

1
2
#include <stdio.h>
int fclose (FILE *stream);

关闭所有和当前进程相关的流接口:
1
2
3
#define _GNU_SOURCE
#include <stdio.h>
int fcloseall (void);

该函数是Linux特有的。

从流中读取数据

单字节读取

1
2
#include <stdio.h>
int fgetc (FILE *stream);

从流中读取下一个字符,并将该无符号字符强转为int返回。

将字符放在流中

1
2
#include <stdio.h>
int ungetc (int c, FILE *stream);

类似于一个栈,后放入先被读出,一个简单的例子能够很好的表示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <ctype.h>
/*
http://c.biancheng.net/cpp/html/269.html
*/
int main()
{
char c ;
while( (c = getchar()) != EOF && isdigit(c) )
{
printf("%c ", c) ;
}
ungetc(c, stdin) ;
ungetc('c', stdin) ;

printf("%c \n", getchar()) ;
return 0 ;
}

1
2
3
icepng@ubuntu:~$ ./test 
12345qwe
1 2 3 4 5 c

按行读取

读取一个字符串

1
2
#include <stdio.h>
char * fgets (char *str, int size, FILE *stream);

从流中最多读取size-1个字节,并存入str中。当所有字节读入时,空字符会被存入字符串末尾。当读到EOF或换行符时读入结束,若读到一个换行符,则将”\n”存入。
也就是说最后一个会放入’\0’。例如:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
char c[10] ;

fgets(c, 10, stdin) ;

if( c[5] == '\0' ) puts("yes") ;
return 0 ;
}

1
2
3
icepng@ubuntu:~$ ./test 
1234
yes

可以得知,c[0-3]存入1-4,c[4]存入’\n’,c[5]存入’\0’。

读取二进制文件

1
2
#include <stdio.h>
size_t fread (void *buf, size_t size, size_t nr, FILE *stream);

从流中读入 nr个数据,每个数据size个字节。返回读入元素的个数。

向流中写数据

和读对应的一些接口

写入单个字符

1
2
#include <stdio.h>
int fputc (int c, FILE *stream);

写入字符串

1
2
#include <stdio.h>
int fputs (const char *str, FILE *stream);

写入二进制数据

1
2
#include <stdio.h>
size_t fwrite (void *buf, size_t size, size_t nr, FILE *stream);

定位流

目的是用于指定位置读写

1
2
#include <stdio.h>
int fseek (FILE *stream, long offset, int whence);

类似于系统I/O中的lseek,偏移也是由whence和offset共同决定的。成功返回0,失败返回-1。
fsetpos函数则用于直接定位到指定位置:
1
2
#include <stdio.h>
int fsetpos (FILE *stream, fpos_t *pos);

rewind函数用于重置流到初始位置:
1
2
#include <stdio.h>
void rewind (FILE *stream);

ftell用于返回当前流的位置:
1
2
#include <stdio.h>
long ftell (FILE *stream);

类似功能的接口为fgetpos,用于获取流的当前位置,并写入pos中:
1
2
#include <stdioh.h>
int fgetpos (FILE *stream, fpos_t *pos);

参考:http://www.runoob.com/cprogramming/c-function-fgetpos.html

清洗一个流

即将用户缓冲区的数据写入到内核之中。

1
2
#include <stdio.h>
int fflush (FILE *stream);

错误与文件结束

一些接口在遇到文件结束EOF和发生错误时返回的一样,如fread等。
用于测试是否在流中设置了错误标志的ferror:

1
2
#include <stdio.h>
int ferror (FILE *stream);

测试文件结束标志是否设置的feof:
1
2
#include <stdio.h>
int feof (FILE *stream);

为流清空错误和文件结尾标志的clearerr;
1
2
#include <stdio.h>
void clearerr (FILE *stream);

获得关联的文件描述符

由流获取到文件描述符,和fdopen正好是相反的功能:

1
2
#include <stdio.h>
int fileno (FILE *stream);

返回和流关联的文件描述符,失败时返回-1.

控制缓冲

即对用户缓冲区的控制,可以设置为不缓冲、行缓冲、块缓冲
行缓冲:在每次遇到换行符,缓冲区将被提交给内核缓冲区。(标准输出stdout,即屏幕输出,默认是行缓冲)
块缓冲:默认所有和文件相关的流都是块缓冲。块缓冲又称为全缓冲。

1
2
#include <stdio.h>
int setvbuf (FILE *stream, char *buf, int mode, size_t size);

模式用于设置三种缓冲形式:
_IONBF:无缓冲
_IOLBF:行缓冲
_IOFBF:块缓冲
buf指向一个size大小的缓冲区空间。无缓冲模式不考虑。如果buf为空,缓冲区则有glibc自动分配。
成功时返回0,不成功返回非0.
注意:在流关闭前,缓冲区必须存在,有时候设置buf为局部变量在作用域结束后导致缓冲区销毁;需注意在之前关闭流。

其他

标准C多线程访问,本质上是线程安全的,即多线程访问,默认会加锁处理。也可以人为加锁。

Linux 系统编程导读 - 高级文件I/O Linux 系统编程概述
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×