您的位置:首页 > 运维架构 > Linux

Linux操作系统-标准IO库(1)

2015-08-04 11:38 656 查看

Linux操作系统—标准I/O库(1)(2015-8-3)

分类:Linux操作系统

  不仅在linux,在很多操作系统上都实现了标准I/O库,该库由ANSI C标准说明。标准I/O库是在系统调用函数基础上构造的,它处理很多细节(例如缓存分配)以优化执行I/O。与基于文件描述符的I/O相比,基于流的I/O更加简单,方便,也更加高效。因而在Linux环境C程序的编写中,基于流的I/O使用更为广泛。

流和文件指针

  在基于文件描述符中的底层系统调用的I/O操作中,所有的I/O函数都是针对文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该问价描述符就用于后续的I/O操作。

  在标准I/O库中,所有的I/O操作都是围绕流(stream)来进行的。在Linux中,文件和设备都可以看做是数据流。

  什么是数据流?数据流是指无结构的字节序列。当用标准I/O库打开或创建一个文件时,即将一个流与一个文件结合起来。

  标准I/O库提供了函数fopen用于打开一个流。当打开一个流时,该函数会返回一个指向FILE对象的指针,即文件指针(类型为FILE *)。FILE对象通常是一个结构,它包含了I/O库为管理该流所需要的所有信息:用于实际I/O的文件描述符,指向流缓存的指针,缓存长度,当前在缓存中的字节数,出错标志等。但一般应用程序没有必要关心FILE结构体的各成员值,使用流时,只需要将FILE指针作为参数传递给每个标准I/O函数。

  Linux对一个进程预定义了三个流:标准输入流,标准输出流和标准错误输出流,它们自动地为进程打开并可用。这三个标准I/O流通过在头文件
<stdio.h>
中预定义的文件指针stdin,stdout和stderr加以引用。它们与文件描述符STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO所表示的标准输入,标准输出和标准错误输出相对应。

缓存

  为什么要有缓存?

  标准I/O库(注意:标准I/O库是在系统调用函数基础上构造的)提供缓存的目的是尽可能减少使用read和write调用的次数,以提高I/O效率。标准I/O也对每个I/O流自动进行缓存管理,免除了由应用程序考虑这一点的麻烦。

  标准I/O提供了三种类型的缓存。

- 全缓存

  使用全缓存时,只有当标准I/O缓存填满后才进行实际的I/O操作。

  对磁盘文件的标准I/O操作一般是实施全缓存的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc函数分配所需要使用的缓存。

  术语刷新(flush)用于说明标准I/O缓存的写操作。缓存可由标准I/O例程自动刷新(比如当填满一个缓存时),或者可以调用函数fflush显示刷新一个流。

行缓存

  使用行缓存时,标准I/O库会在输入和输出中遇到换行符时执行实际的I/O操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。

  对行缓存有两个限制:

1. 因为行的缓存长度是固定的,所以只要填满了缓存,即使还没有写一个换行符,也会进行I/O操作。

2. 任何时候只要通过标准I/O库要求从一个不带缓存的流或一个行缓存的流(它预先要求从内核中得到数据,所需要的数据可能已在该缓存中)得到输入的数据,那么就会造成刷新所有的行缓存输出流。

不带缓存

  即不对字符进行缓存。如果用标准I/O函数写若干条字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。标准错误输出流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。

ANSI C规定了下列缓存特征:

- 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。

- 标准错误输出绝对不会是全缓存的。

流的打开和关闭

  标准I/O库提供了fopen系列函数用以创建或打开流文件,提供了fclose函数关闭已打开的流文件。

打开流

  使用fopen系列函数可以创建或打开流文件,这些函数的原型如下。

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);


  这三个函数的区别如下:

(1)fopen打开路径名由path指定的一个文件。

(2)freopen在由stream指定的流上打开一个指定的文件(其路径由path指定),如若该流已经打开,则先关闭该流。次函数一般用于将一个指定的文件打开为一个预定义的标准流:标准输入,标准输出或者标准错误输出。

(3)fdopen取一个现存的文件描述符,并使一个标准的I/O流与该描述相结合。此函数常用于创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准I/O函数fopen打开,而必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符相结合。注意:fdopen函数不是ANSI C的标准函数,而是属于POSIX.1的标准。

  这三个函数各参数和返回值的含义如下:

1. path:要打开或创建的文件的名字

2. mode:对该I/O流的读,写方式,ANSI C规定了15种不同的可能值

r或rb:以读方式打开

w或wb:以写方式打开或创建,并将文件长度截为0

a或ab:以写方式打开,新内容追加在文件尾

r+或r+b或rb+:以更新方式打开(读和写)

w+或w+b或wb+:以更新方式打开,并将文件长度截为0

a+或a+b或ab+:以更新方式打开,新内容追加在文件尾

  注意:(1)字符b的作用是区分文本文件和二进制文件。但Linux内核并不对这两种文件进行区分,所以在Linux系统环境下字符b作为mode的一部分实际上并无作用。(2)对于fdopen,因为该描述符已经被打开,即所引用的文件必已存在,所以fdopen以写方式打开并不会创建该文件或截短该文件。

3-fd:待关联的底层文件描述符

4-stream:待关联的流的文件指针,若该流已经打开则会被先关闭

5-返回值:成功时返回流文件的指针,失败时返回NULL

  能否成功打开文件流受打开方式的限制,如下表所示。另外,进程可打开的文件流的数量有一个上限,该上限值由stdio.h中定义的FOPEN_MAX常量定义。

  打开一个I/O流的六种不同方式的限制

限制rwar+w+a+
文件必须已存在
擦除文件以前的内容
流可读
流可写
流只可在尾端写
  在指定w或a类型创建一个新文件时,无法指定该文件的存取许可权位,POSIX.1要求这种方式创建的文件具有以下存取许可权。

S_USR | S_WUSR | S_RGRP | S_WGRP | S_ROTH | S_WOTH

  除非流引用终端设备,否则系统默认它被打开时是全缓存的。若流引用终端设备,则该流四行缓存的。

关闭流

  在使用完流文件后,应调用fclose函数关闭流。fclose函数原型如下:

#include <stdio.h>
int fclose(FILE *fp);


  函数成功时返回0,失败时返回EOF(-1)

  在文件流被关闭之前,fclose会刷新缓存中的输出数据,但缓存中的输入数据将被丢弃。如果标准I/O库已经为该流自动分配了一个缓存,则释放此缓存。

  当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I/O流都被刷新,所有打开的标准I/O流都被关闭。

实践篇

   目标一:在当前目录下创建一个file.txt文件

方法一:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
FILE *fp = NULL;
fp = fopen("file.txt", "w");

if (fp == NULL){
printf("Cannot create files!\n");
exit(-1);
}
fclose(fp);

return 0;
}


  该程序能够达到预定目标,它在我的机器上的运行结果如下:

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gedit fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o fopen fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./fopen
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
file.txt  fopen  fopen.c  fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ll file.txt
-rw-rw-r-- 1 biantiao biantiao 0  8月  4 08:57 file.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$


运行结束后,它在当前目录下创建了一个空的file.txt文件。

方法二:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
FILE *fp = NULL;
fp = fopen("file.txt", "r");

if (fp == NULL){
printf("Cannot create files!\n");
exit(-1);
}
fclose(fp);

return 0;
}


  该方法和上面的区别是,fopen中的参数由w换成了r,结果怎样呢?结果是不行的,它在我的机器上的运行结果如下:

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gedit fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o fopen fopen.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
fopen  fopen.c  fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./fopen
Cannot create files!
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ll
总用量 28
drwxr-xr-x  2 biantiao biantiao 4096  8月  4 09:04 ./
drwxr-xr-x 46 biantiao biantiao 4096  8月  3 21:41 ../
-rwxrwxr-x  1 biantiao biantiao 8704  8月  4 09:04 fopen*
-rw-rw-r--  1 biantiao biantiao  220  8月  4 09:01 fopen.c
-rw-rw-r--  1 biantiao biantiao  220  8月  4 08:56 fopen.c~
biantiao@lazybone1994-ThinkPad-E430:~/桌面$


  应该能够明白创建文件该用哪个参数了吧。注意w和r的区别,如下:

r或rb:以读的方式打开(注意只能打开,不能创建)

w或wb:以写方式打开或创建,并将文件长度截为0

  写到这里我又想写一个小程序来验证上面的话,真心不好意思,初学者好奇心很重。说干就干。验证什么呢?验证以w方式打开一个文件能将其长度截为0。首先先在当前目录创建一个test.txt文件,在里面输入一句hello world。如下图:



  编写下列程序,保存为cut.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
FILE *fp = NULL;
fp = fopen("test.txt", "w");

if (fp == NULL){
printf("Open file failed!\n");
exit(-1);
}
fclose(fp);

return 0;
}


  从上面的程序可以看出,代码只是将已存在的test.txt文件打开,然后关闭。看看test.txt文件会有什么变化。原先test.txt文件中写有一句Hello World!。编译并运行,查看结果。

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o cut cut.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ls
cut  cut.c  test.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./cut
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ cat test.txt
biantiao@lazybone1994-ThinkPad-E430:~/桌面$


  从命令行中cat的结果来看,test.txt文件确实被截短为0了。为了更直观我用gedit打开看看。如下图:



文件确实被截短为0了!(下次编程时要注意了,如果不想文件原来的内容被截掉,不能使用w参数,而得使用a或r参数来实现)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: