一,背景介绍
狭义的文件存放在磁盘上,广义上在Linux下一切皆文件;磁盘上的文件一般为永久存储的外设,本质上对文件的操作,即为对外设的输入和输出(简称I/O);空文件并不是不占磁盘文件,只是没有内容,文件=属性(元数据)+内容;对文件的内容操作,如fread、fwrite、fgets、fgetc,fputc,fputs等;对文件的属性操作,如fseek、ftell、rewind等;
从系统角度,对文件的操作,其实就是进程对文件的操作;是通过系统调用接口,来实现操作的;
当前路径,每个进程都有一个内置属性cwd(即当前工作目录);
bash
[wz@192 ~]$ ll /proc/29286
total 0
dr-xr-xr-x. 2 wz wz 0 8月 7 19:11 attr
-rw-r--r--. 1 wz wz 0 8月 7 19:11 autogroup
-r--------. 1 wz wz 0 8月 7 19:11 auxv
-r--r--r--. 1 wz wz 0 8月 7 19:11 cgroup
--w-------. 1 wz wz 0 8月 7 19:11 clear_refs
-r--r--r--. 1 wz wz 0 8月 7 19:10 cmdline
-rw-r--r--. 1 wz wz 0 8月 7 19:11 comm
-rw-r--r--. 1 wz wz 0 8月 7 19:11 coredump_filter
-r--r--r--. 1 wz wz 0 8月 7 19:11 cpuset
lrwxrwxrwx. 1 wz wz 0 8月 7 19:11 cwd -> /home/wz/Desktop
-r--------. 1 wz wz 0 8月 7 19:11 environ
lrwxrwxrwx. 1 wz wz 0 8月 7 19:11 exe -> /home/wz/Desktop/target
dr-x------. 2 wz wz 0 8月 7 19:10 fd
...
在任何C程序,都会默认打开三个文件(硬件):
- 标准输入,stdin,键盘文件;
- 标准输出,stdout,显示器文件;
- 标准错误,stderr,显示器文件;
注,所有外设硬件,本质上即对应read/write的核心操作;可通过C接口,直接对stdin/stdout/stderr进行读写操作;默认打开这三个文件,其他函数使用需求,如scanf、printf、perror等,另外fprintf、fscanf;其他语言也是如此,C++中是cin、cout、cerr;
二,系统接口函数
open、close、read、write;
open 打开或创建文件
close 关闭文件
read 读取文件
write 写入文件
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);
if(fd<0)
{
perror("open");
return -1;
}
const char* msg = "hello world\n";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("log.txt", O_RDONLY);
if(fd<0)
{
perror("open");
return -1;
}
char buffer[1024];
ssize_t s = read(fd, buffer, sizeof(buffer)-1);
if(s>0)
{
buffer[s] = '\0';
printf("%s\n", buffer);
}
close(fd);
return 0;
}
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("log.txt", O_WRONLY|O_APPEND);
if(fd<0)
{
perror("open");
return -1;
}
const char* msg = "hello world append\n";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
注:
- O_WRONLY|O_CREAT 对应C语言 w ;
- O_RDONLY 对应C语言 r ;
- O_WRONLY|O_APPEND 对应C语言 a ;
标签flags,O_WRONLY、O_CREAT、O_RDONLY、O_APPEND都是宏;
cpp
/usr/include/asm-generic/fcntl.h
#define O_ACCMODE 00000003
#define O_RDONLY 00000000
#define O_WRONLY 00000001
#define O_RDWR 00000002
#ifndef O_CREAT
#define O_CREAT 00000100 /* not fcntl */
#endif
#ifndef O_EXCL
#define O_EXCL 00000200 /* not fcntl */
#endif
#ifndef O_NOCTTY
#define O_NOCTTY 00000400 /* not fcntl */
#endif
#ifndef O_TRUNC
#define O_TRUNC 00001000 /* not fcntl */
#endif
#ifndef O_APPEND
#define O_APPEND 00002000
#endif
编程语言使用自己的接口,封装系统接口,是因为需兼容自身语法特征,且系统调用使用成本较高,不具备可移植性;
三,文件描述符
Linux进程默认会有三个已打开的文件描述符,即0(stdin键盘)、1(stdout显示器)、2(stderr显示器);系统接口函数的返回值即为文件描述符,是从0开始的小整数,实际上是数组的下标;新文件的文件描述符使用当前没被使用的最小下标;
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd1 = open("log1.txt", O_WRONLY|O_CREAT, 0644);
int fd2 = open("log2.txt", O_WRONLY|O_CREAT, 0644);
int fd3 = open("log3.txt", O_WRONLY|O_CREAT, 0644);
int fd4 = open("log4.txt", O_WRONLY|O_CREAT, 0644);
printf("fd1=%d\n",fd1);
close(fd1);
printf("fd2=%d\n",fd2);
printf("fd3=%d\n",fd3);
printf("fd4=%d\n",fd4);
return 0;
}
bash
//新建的文件会从3开始
[wz@192 Desktop]$ ./target
fd1=3
fd2=4
fd3=5
fd4=6
结构体指针files
- 进程task_struct包含一个结构体指针files;
结构体指针files指向的表files_struct
- 该表包含一个结构体指针数组;
- 该数组中元素指向打开的文件;
文件结构体
四,输出重定向
cpp
//关闭默认打开的1指向的文件,即stdout
//此时新创建的文件,fd将会是1
//本来printf应打印到显示器,此时应该写入到log.txt
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);
printf("fd=%d\n",fd);
close(fd);
return 0;
}
bash
//此时不仅没有打印到屏幕,也没有写入到log.txt
[wz@192 Desktop]$ ./target
[wz@192 Desktop]$ cat log.txt
[wz@192 Desktop]$
FILE
- FILE结构体内部包括,文件对应的文件描述符下标fd 、及应用层C语言提供的缓冲区数据;
- 向普通文件写入是全缓冲,向屏幕文件写入是行缓冲;
cpp
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);
printf("fd=%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
bash
//刷新到了log.txt文件
[wz@192 Desktop]$ ./target
[wz@192 Desktop]$ cat log.txt
log.txt fd=1
cpp
//语言层次上
int main()
{
printf("stdin fd=%d\n",stdin->_fileno);
printf("stdout fd=%d\n",stdout->_fileno);
printf("stderr fd=%d\n",stderr->_fileno);
FILE* fp = fopen("log.txt", "r");
printf("log.txt fd=%d\n",fp->_fileno);
pclose(fp);
return 0;
}
bash
[wz@192 Desktop]$ ./target
stdin fd=0
stdout fd=1
stderr fd=2
log.txt fd=3