标准IO和文件IO
一、库和系统调用
操作系统内核提供的接口函数就是系统调用。
库是别人写好的现有的、成熟的,可以代码复用的代码,如C库,数学函数库等等。很多库中的函数是对内核提供的系统调用的封装。系统调用对内核的依赖性很大,都是Linux操作系统,如redhat 、Ubuntu,但是他们提供的系统调用有些是不同的,linux和windows的系统调用也不同
看看维基百科如何解释:
在计算机中,系统调用(英语:system call),又称为系统呼叫,指运行在使用者空间的程序向操作系统内核请求需要更高权限运行的服务。 系统调用提供了用户程序与操作系统之间的接口。大多数系统交互式操作需求在内核态执行。如设备IO操作或者进程间通信。
采用系统调用编写的程序,可移植性不好,但是稳定。
![20130302_154924_thumb9 20130302_154924_thumb9](http://images0.cnblogs.com/blog/480488/201303/03114948-0f8f97e03c8a4bb4ac1c12c18642fa14.png)
为了增强程序的可移植性,在App和系统调用之间加入一层库,如C库,库提供给App的接口是固定的,但是针对不同的操作系统,库的内部实现会有不同。这样App只需要调用库提供的函数即可,当换到另一个操作系统环境下时,只需要将应用程序重新编译一下即可。
![2013-03-02_160410_thumb32 2013-03-02_160410_thumb32](http://images0.cnblogs.com/blog/480488/201303/03114949-b21c0cec9a2644ed994227a5ba0d9d47.png)
二、内核空间和用户空间
linux中每个进程可以访问4G的空间,则4G空间又分为内核空间和用户空间。
![20130302_164848_thumb 20130302_164848_thumb](http://images0.cnblogs.com/blog/480488/201303/03114949-cd425a8acd7440ea825298633d5c30e7.png)
三、用户态和内核态
内核态有称呼为系统态、核心态。是用来描述计算机CPU的状态。
如果正在执行系统调用,则CPU处于内核态;否则,处于用户态。
四、文件IO和标准IO
打开一个文件可以使用文件IO,也可以使用标准IO。
标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。如printf、fopen等等。标准I/O默认采用了缓冲机制。
文件IO:文件IO称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
文件IO:(非缓冲,底层IO)
比如:
当执行语句int fd = open(“1.c”,……);后,在内核空间会产生一个struct file的结构体(用户无法访问这个结构体),里面存储了关于这个文件的很多信息。其中,open就是系统调用,也就是文件IO,它是操作系统内核提供的函数,open返回一个关于这个文件的非零整数(struct file 的一个成员),称为文件描述符。示意图:
![image_thumb14 image_thumb14](http://images0.cnblogs.com/blog/480488/201303/03114951-d9c109a471b143c098c754ed2d868936.png)
标准IO:(缓冲,高级 IO)
如: FILE * fp = fopen(“1.c”,"r”);其中fopen是C库提供的一个函数,fopen函数在实现时又调用了open函数。同样,在内核空间会产生一个struct file的结构体(用户无法访问这个结构体),同时在用户空间也会产生一个结构体struct _IO_FILE,该结构体中其中有一个参数fd,它是open的返回值。同时该结构体会生成两个缓冲区,分别称为输入缓冲区(从磁盘到内存)和输出缓冲区(从内存到磁盘)。fopen将返回一个指向结构体struct _IO_FILE的指针,类型是FILE *,我们称它为 “流”。示意图:
![image_thumb20 image_thumb20](http://images0.cnblogs.com/blog/480488/201303/03114951-78a99c709b9145b69af96fc486c88db5.png)
即,调用标准IO打开文件,会在内存中产生两个结构体,一个在用户空间,一个在内核空间。流fp指向的是用户空间的那个结构体,类型是FILE *。
五、全缓冲、行缓冲以及不缓冲(针对的是标准IO)
Linux中文件流中处理的是二进制序列。即所有的字符都用一个字节的二进制ASCII码表示。若是数值则用对应的二进制表示,对 ‘\n’不转换。
全缓存
:当处理的是文件流是采用全缓存。如 fopen(“1.c”,"r”);
全缓存的含义:(如当调用fwrite、fprintf、fputs、fputc)只有当与相应文件对象的输出缓冲区满或者满足一定的条件时,才进行实际的写操作(将输出的内容写到磁盘中),否则,输出的内容一直在FILE 类型的结构体的输出缓冲区中,即还在内存中。
下面是全缓存发生实际IO操作的条件:
- 输出缓冲区满;
- 在输出缓冲区未满时,调用了fflush(fp);fp是要写入的文件的文件流;
- 在输出缓冲区未满时,调用了fclose(fp);fp是要写入的文件的文件流;
- 程序结束。这里指main函数执行完。
行缓存
:当处理的流是终端时,这里的终端指的是标准输入stdin()和标准输出stdout。
标准输入stdin:比如 键盘、串口
标准输出stdout:比如 显示器、串口
这里需要指出,Linux中行缓冲区的大小默认是1024个字节。
标准输入stdin:可以这么理解,注其中fd固定为0
![image_thumb44 image_thumb44](http://images0.cnblogs.com/blog/480488/201303/03114952-60bbef22298a41278661287fc2dcaa56.png)
标准输入stdin:可以这么理解,注其中fd固定为1
![image_thumb49 image_thumb49](http://images0.cnblogs.com/blog/480488/201303/03114953-c9c5fd36332d456bb2ddb0e480a19688.png)
行缓冲从缓冲区中读取数据的条件:
- 缓冲区满;
- 遇到 ‘\n’,即遇到回车符;
- 调用fflush(stdout)刷新输出;
- 调用fclose(stdout)关闭输出流;
- 程序结束,即main函数执行完;
下面看一个程序:
#include <stdio.h>
int main(void)
{
printf("137\n");
printf("pengdonglin");
while(1);
return 0;
}
![image_thumb2 image_thumb2](http://images0.cnblogs.com/blog/480488/201303/03114954-796d2fada8d34402a9d41358f7909b6c.png)
可以看到,只输出了137,而没有输出pengdonglin。
分析:执行pringf(“137\n”);时,137先存到缓冲区,当遇到\n时,将缓冲区中的内容(\n已经在缓冲区),输出到屏幕。缓冲区为空,执行printf("pengdonglin");时,先将pengdonglin输出到缓冲区,由于没有\n,程序有没有结束而是执行while(1),死循环,所以没有输出pengdonglin。
将代码改成下面:
#include <stdio.h>
int main(void)
{
printf("137\n");
while(1)
printf("pengdonglin");
return 0;
}
![image_thumb3 image_thumb3](http://images0.cnblogs.com/blog/480488/201303/03114955-607aad2a32704b879ddbe3e3db42023f.png)
这下为什么就输出了?因为计算机不断地执行printf("pengdonglin");,很快stdout的输出缓冲区就满了,结果就输出了。
还用该注意的是输入函数scanf 、gets 、getchar、getc等都处理的是stdin,所以他们的输入缓冲区是同一个。这也就解释了,下面的程序,
#include <stdio.h>
int main(void)
{
char buffer2[10];
int ch1;
int data1;
printf("%d\n",data1);
scanf("%d",&data1);
ch1 = getchar();
scanf("%s",buffer2);
printf("data1=%d\nch1=%c\nbuffer2=%s\n",data1,ch1,buffer2);
return 0;
}
当输入137pengdonglin时:
![image_thumb4 image_thumb4](http://images0.cnblogs.com/blog/480488/201303/03114956-e506bb50de304710a717c0794860e5c3.png)
可以看到,输入一串字符,回车后,只有输入缓冲区不为空,下面的scanf、getchar都会执行。
在这里我们还看到了scanf的一些规则,当回车后,scanf逐一扫描流stdin的输入缓冲区的中的内容,如果与要求的符合那么就读出,不符合直接跳过当前的scanf函数,执行下一条语句,变量原来的只保持不变。还是上面的程序,当输入pengdonglin时:
![image_thumb5 image_thumb5](http://images0.cnblogs.com/blog/480488/201303/03114956-84642a79f6024a9c9f6308686bd349df.png)
可以看到,data1的原值是134513984,输入pengdonglin回车后,scanf发现第一个字符时p而不是数字,所以中止执行scanf("%d",&data1);接着执行下一条语句。
再看下面类似的例子:
#include <stdio.h>
int main(void)
{
char buffer2[10];
int ch1;
int data1;
int data2;
printf("%d\n",data1);
scanf("%d",&data1);
ch1 = getchar();
scanf("%d",&data2);
scanf("%s",buffer2);
printf("data1=%d\nch1=%c\ndata1=%d\nbuffer2=%s\n",data1,ch1,data2,buffer2);
return 0;
}
![image_thumb6 image_thumb6](http://images0.cnblogs.com/blog/480488/201303/03114957-2f80f59519864fb5b2380a3a0fe1fa57.png)
无缓冲
指的是stderr(标准出错),即只要输出到这个流,就立刻输出,无缓冲。
可以这么理解stderr: 注其中fd固定为2
![image_thumb7 image_thumb7](http://images0.cnblogs.com/blog/480488/201303/03114958-b8ddbbed0d4e4936a7fffe865f5681d8.png)
如下面的代码:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void)
{
FILE *fp;
if((fp = fopen("1.c","r")) == NULL)
{
fprintf(stderr,"open file 1.c error! %s\n",strerror(errno));
}
return 0;
}
![image_thumb8 image_thumb8](http://images0.cnblogs.com/blog/480488/201303/03114959-a359cf5165f64982a72a6560269e12ea.png)
本文来自博客园,作者:dolinux,未经同意,禁止转载
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步