1.C标准文件io函数工作流程 图左边是C标准文件IO函数
如果要对文件进行操作,必须先fopen这个文件,用fopen打开hello.txt文件的时候,如果成功,则会返回一个struct FILE结构体的地址,这个结构体有三个核心要素,第一个是文件描述符,第二个是文件读写指针位置,第三个是文件缓冲区,I/O缓冲区。
文件描述符是一个整型的数,例如1、2、3、4,这个文件描述符与linux内核有关,可以用它来直接索引到真正的磁盘文件(姑且先这么理解)
文件读写指针位置:记录文件读写到什么位置,当使用fread,fwrite等函数对文件进行读写操作是,他就会记录文件指针位置。
文件缓冲区:打开一个文件会自动打开一个文件缓冲区,默认大小8192Bytes,在内存中建立的;作用就是缓存往文件中写的数据,当满足条件时,刷新到磁盘中;
刷新条件:调用fflush函数
数据大到超过缓冲区大小
正常关闭文件,fclose,return(main),exit(main)
C标准库在当你运行一个程序的时候,会默认打开三个FILE类型的指针:stdin(标准输入)、stdout(标准输入)、stderr(标准出错),也都有自己对应得文件描述符,文件读写指针位置,I/O缓冲区
以printf函数来时,它是不能直接调用电脑的显示屏的,它是先调用操作系统的API函数,再由操作系统的API函数调用硬件层的显示屏,其中操作系统又分为应用层和内核层。
以linux系统为例,printf函数调用了应用层的write函数,格式write(fd, "hello", 5),fd是文件描述符(file dis),“hello”,5是5个字节;printf也会默认打开stdin(标准输入)、stdout(标准输入)、stderr(标准出错)三个函数,printf用的是stdout,把stdout的fd传给write,但是要想输入到显示屏上要内核层的设备驱动函数sys_write();通过sys_write函数,就可以把hello输入到屏幕上了。
write这个函数是没有缓冲区的,但是如果write一个“hello”,在内核层是有缓冲区的,这个缓冲区是有设备驱动函数来维护的。
c标准库缓冲区与内核缓冲区的区别
fwrite一个“hello”,首先存到c标准库缓冲区,这个时候如果进行fread是读不到这个hello的,因为这个时候数据还在C标准缓冲区存着,并没有通过write写到内核缓冲区和磁盘上,所以是读不到的;
write一个“hello”,通过函数的层层调用写到内核缓冲区,write就可以返回了,这个时候内核缓冲区是就有一个叫守护进程/线程,通过守护进程的定时刷新机制,把数据写到设备文件(磁盘、显示屏、网络)上去,
调用fopen会生成一个文件缓冲区,这个文件缓冲区是有C标准提供的,与操作系统无关。
条件:上图是以linux32位操作系统为例
在linux上的每个运行的程序(进程),系统都会给他分配0~4G的地址空间(虚拟地址空间)
3G~4G:成为内核空间(kernel):运行的linux内核;内存管理,进程管理,设备驱动管理,VFS虚拟文件系统等(受保护,用户不可访问)
如果用户在linux上运行一个./a.out的时候,它是运行在0~3G的
4k~3G的地址划分:TEXT(二进制机器指令);DATA(已初始化的全局变量);BBS(未初始化的全局变量);堆空间(malloc等申请的空间);共享库/dll动态库(C标准库,系统库);栈空间(局部变量、数组等);命令行参数(main(int argc, char *argv[])中argv字符串数组的存储位置);环境变量(env)
0~4k:受保护地址 #define NULL (void*)0
当程序内存访问越界到内核空间是会报段错误
1 #include <stdio.h>
2
3 int main(void)
4 {
5 *((unsigned int*)0xcf000000) = 0x12345678;
6 return 0;
7 }
段错误(核心已转移)
linxu可执行文件的格式:ELF
文件描述符
PCB:进程控制块
每个进程度维护了自己的一个文件描述符表
PCB里面有个files_struct的一个指针,这个指针指向一个文件描述符表(类似一个文件描述符表),
stdin的文件描述符是0(读),stdout是1(写),stderr是2;0,1,2都是指向当前终端/dev/tty,每个终端都是相对独立的,都有单独的0,1,2.
printf调用的是write,write的文件描述符是stdout(1);
文件描述符表存储在内核空间;准则:新分配的文件描述符是文件描述符表里面未使用的最小的那个,默认情况下,一个进程最多打开1024个文件(0~1023),shell命令:ulimit -a查看,-n修改;
linux应用层打开文件的函数int fd = open("hello.txt", O_RWRD);返回一个整型的值,这个值就是文件描述符里的值,因为这个时候进程已经自己打开了0,1,2三个文件描述符,所以hello.txt的open函数返回的整型是3;3会和hello.txt磁盘文件产生关联.
当再打开一个“world.txt”,这时候他的文件描述符是4,再close(fd);把“hello.txt”关闭,再打开一个“aaa.txt”,根据文件描述符的准则,这个时候“aaa.txt”的文件描述符是3。
系统API函数
open/close
open
SYNOPSIS #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); RETURN VALUE |
flags 一个32位的进制数
必选项:以下三个常数中必须指定一个,且仅允许指定一个。
* O_RDONLY 只读打开
* O_WRONLY 只写打开
* O_RDWR 可读可写打开
以下可选项可以同时指定0个或多个,和必选项按位或起来作为flags参数。
* O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
* O_CREAT 若此文件不存在则创建它。使用此选项是需要提供第三个参数mode,表示该文件的访问权限。
* O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
* O_TRUNC 如果文件已存在,并且以O_WRONLY或O_RDWR方式打开,则将其长度截断为0字节。
* O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开,可以做非阻塞I/O
在/usr/src/linux-headers-3.2.0-20/include/fcntl.h中宏定义
19 #define O_RDONLY 00000000 20 #define O_WRONLY 00000001 21 #define O_RDWR 00000002 22 #ifndef O_CREAT 23 #define O_CREAT 00000100 /* not fcntl */ 24 #endif 25 #ifndef O_EXCL 26 #define O_EXCL 00000200 /* not fcntl */ 27 #endif 28 #ifndef O_NOCTTY 29 #define O_NOCTTY 00000400 /* not fcntl */ 30 #endif 31 #ifndef O_TRUNC 32 #define O_TRUNC 00001000 /* not fcntl */ 33 #endif 34 #ifndef O_APPEND 35 #define O_APPEND 00002000 36 #endif 37 #ifndef O_NONBLOCK 38 #define O_NONBLOCK 00004000 39 #endif |
mode:就是新创建文件的权限为,可以结合chmod命令来理解;创建的时候注意umask。
文件描述符
一个进程默认打开3个文件描述符
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
open函数打开文件错误会返回-1,并且设置一个errno,errno的宏定义在
/usr/include/asm-generic/errno-base.h
4 #define EPERM 1 /* Operation not permitted */ 5 #define ENOENT 2 /* No such file or directory */ 6 #define ESRCH 3 /* No such process */ 7 #define EINTR 4 /* Interrupted system call */ 8 #define EIO 5 /* I/O error */ 9 #define ENXIO 6 /* No such device or address */ 10 #define E2BIG 7 /* Argument list too long */ 11 #define ENOEXEC 8 /* Exec format error */ 12 #define EBADF 9 /* Bad file number */ 13 #define ECHILD 10 /* No child processes */ 14 #define EAGAIN 11 /* Try again */ 15 #define ENOMEM 12 /* Out of memory */ 16 #define EACCES 13 /* Permission denied */ 17 #define EFAULT 14 /* Bad address */ 18 #define ENOTBLK 15 /* Block device required */ 19 #define EBUSY 16 /* Device or resource busy */ 20 #define EEXIST 17 /* File exists */ 21 #define EXDEV 18 /* Cross-device link */ 22 #define ENODEV 19 /* No such device */ 23 #define ENOTDIR 20 /* Not a directory */ 24 #define EISDIR 21 /* Is a directory */ 25 #define EINVAL 22 /* Invalid argument */ 26 #define ENFILE 23 /* File table overflow */ 27 #define EMFILE 24 /* Too many open files */ 28 #define ENOTTY 25 /* Not a typewriter */ 29 #define ETXTBSY 26 /* Text file busy */ 30 #define EFBIG 27 /* File too large */ 31 #define ENOSPC 28 /* No space left on device */ 32 #define ESPIPE 29 /* Illegal seek */ 33 #define EROFS 30 /* Read-only file system */ 34 #define EMLINK 31 /* Too many links */ 35 #define EPIPE 32 /* Broken pipe */ 36 #define EDOM 33 /* Math argument out of domain of func */ |
查看这个errno需要用到perror这个函数
SYNOPSIS
RETURN VALUE |
read/write
read
SYNOPSIS
RETURN VALUE
|
write
SYNOPSIS
RETURN VALUE
|
阻塞与非阻塞
阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起。只有函数在得到结果之后才会返回。
非阻塞:非阻塞与阻塞的概念相对应,只在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回
默认情况下打开文件时是阻塞的,要把flags的值位或(|)上一个非阻塞标志(O_NONBLOCK)就可以改变。
lseek
SYNOPSIS #include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
RETURN VALUE
|
linux的shell命令:od -tcx aaa.avi
以16进制和ASIIC码对应的方式查看文件
fcntl
SYNOPSIS RETURN VALUE
|
重新设置文件的flags,而且不用重新打开文件