3.1.errno和perror
(1)errno(error_number即错误号码),linux系统中对各种常见错误做了编号表,当函数执行错误时,函数会返回1个特定的errno编号来告诉我们该函数到底哪里错了(前提是该函数返回时会设置errno);errno是由OS来维护的1个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了1个什么错误。
(2)errno本身实质是1个int类型的数字,每个数字编号对应1种错误,当我们只看errno时只能得到1个错误编号数字(譬如-37);linux系统提供了1个函数perror(print_error),perror函数内部会读取errno并且将其直接转成对应的错误信息字符串,然后print打印出来。
3.2.read和write的count
(1)count和返回值的关系;count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数;实际的有可能等于想要读写的,也有可能小于(说明没完成任务)。
(2)count再和阻塞非阻塞结合起来,就会更加复杂,如果某个函数是阻塞式的,则我们要读取30个字节数据,结果暂时只有20个字节数据时就会被阻塞住,等待剩余的10个字节数据可以读。
(3)有时候我们写正式程序时,我们要读取或者写入的是1个很庞大的文件(譬如文件有2MB),我们不可能把count设置为2*1024*1024,而应该去把count设置为1个合适的数字(譬如2048、4096),然后通过多次读取来实现全部读完。
3.3.文件IO效率和标准IO
(1)文件IO即由open、close、write、read等API函数构成的1套用来读写文件的体系,这套体系可以很好的完成文件读写,但是效率并不是最高的;应用层C语言库函数提供了一些用来做文件读写的函数列表,叫标准IO,标准IO由一系列的C库函数构成(fopen、fclose、fwrite、fread),这些标准IO函数是由文件IO封装而来的(fopen内部其实调用的还是open)。
(2)标准IO加了封装之后主要是为了在应用层添加1个缓冲机制,这样我们通过fwrite写入的内容不是直接进入内核中的buf,而是先进入应用层标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单次write的最佳count来选择好的时机来完成write到内核中的buf,内核中的buf再根据硬盘的特性来选择好的实际去最终写入硬盘中。
3.4.静态文件和inode节点
(1)文件平时都存放在硬盘中,硬盘中存储的文件以1种固定的方式存放,其被称为静态文件。
(2)1块硬盘区域可分为2块区域,1块是硬盘内容管理表项,1块是真正存储内容的区域;操作系统最初手里的信息是文件名,最终得到的是文件内容;操作系统访问文件时首先读取硬盘内容管理表,从中找到目标文件的扇区级别的信息,然后利用该信息去查询真正存储内容的区域,最后得到我们要的文件内容。
(3)硬盘内容管理表中以文件为单位记录了各个文件的各种信息,每个文件都有1个信息列表(即inode,i节点,其实质是1个结构体,该结构体中有很多元素,每个元素记录了该文件的一些信息,包括文件名、文件在硬盘上对应的扇区号、块号…);硬盘资源管理是以文件为单位进行的,每个文件对应1个inode,每个inode对应1个数字编号,每个数字编号对应1个结构体,结构体中记录了该文件的各种信息。
(4)联系平时实践,大家格式化硬盘(U盘)时发现有快速格式化和底层格式化两个选项;快速格式化非常快,格式化1个32GB的U盘只要1秒钟,普通格式化格式化速度慢;快速格式化就是只删除了U盘中的硬盘内容管理表(即inode),真正存储的内容没有动,这种格式化的内容是有可能被找回的。
3.5.动态文件和vnode节点
(1)1个程序的运行就是1个进程,我们在程序中打开的文件就属于某个进程;每个进程都有1个数据结构用来记录该进程的所有信息(即进程表);进程表中有1个指针会指向1个数据结构(即文件描述符表,每个文件描述符表项=fd+相对应的文件表指针),文件描述符表记录了当前进程打开的所有文件的fd及其相对应的文件表指针;文件表指针指向1个数据结构(即文件表,每个文件表=文件状态标志+当前文件位移量即文件指针+V节点指针(指向vnode节点))文件表中记录了与某个fd相对应的文件相关信息;文件描述符表中用来索引各个打开的文件的index就是文件描述符fd(fd->文件表指针->文件表->V节点指针->vnode节点)。
(2)1个vnode中就记录了1个被打开的文件的各种信息(V节点=i节点信息+当前文件长度+对不同种类文件操作的给个函数指针),则我们只要知道该文件的fd,就可以找到该文件的vnode进而对这个文件进行各种操作。
3.6.文件和流的概念
(1)流(stream)对应自然界的水流,在文件操作中,文件类似是1个大包裹,里面装了1堆字符,但文件被读出/写入时都只能1个字符1个字符的进行,而不能1股脑儿的读写,则1个文件中N多的个字符被挨个依次读出/写入时,这些字符就构成了1个字符流。
(2)流这个概念是动态的,不是静态的;编程中提到流这个概念,一般都是IO相关的,所以经常叫IO流;文件操作时就构成了1个IO流。
3.perror
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.youkuaiyun.com/rston
* GitHub:https://github.com/rston
* 项目:文件读写细节及文件管理
* 功能:演示perror函数的使用。
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd = -1; // fd即file descriptor,文件描述符
int ret = -1; // 用于接收read返回值,判断是否读文件成功
char buf[100] = {0}; // 构建缓冲区存放从文件读出的内容
char bufwrite[20] = "linux is great"; // 构建缓冲区存放要写入文件的内容
// 打开test.txt文件,若该文件不存在则创建(权限666),若该文件存在则报错
fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0666);
if (-1 == fd) // 判断文件打开是否成功,也可这样写if (fd < 0)
{
//printf("open file error.\n");
perror("open file error"); // 通过perror捕捉errno错误信息并打印输出
//return -1; // 使用return或exit退出进程或程序
exit(-1);
}
else
{
printf("open file sucess. fd = %d.\n", fd);
}
#if 1
ret = write(fd, bufwrite, strlen(bufwrite)); // 写内容到文件中
if (ret < 0) // 判断内容是否成功写入文件
{
//printf("write file errot.\n");
perror("write file errot");
//return -1;
exit(-1);
}
else
{
printf("write %d bytes to file.\n", ret);
printf("the content of write is [%s].\n", bufwrite);
}
#endif
#if 1
ret = read(fd, buf, sizeof(buf)); // 读取文件内容
if (ret < 0) // 判断读取文件是否成功
{
//printf("read file error.\n");
perror("read file error");
//return -1;
exit(-1);
}
else
{
printf("read %d bytes actual from file.\n", ret);
printf("the content of read is [%s].\n", buf);
}
#endif
close(fd); // 关闭文件
//return 0;
exit(0);
}