目录
c语言IO基础操作:
在我们学习C语言的时候唯一对IO流有过的接触的就是c语言的文件操作了,回顾一下c语言对文件的操作
练习1:向一个文本文件写入5个hello word
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("word.txt", "w");
if(!fp) printf("文件打开失败\n");
int count = 5;
const char* str = "hello word\n";
while(count--){
fwrite(str, strlen(str), 1, fp); //往文件中写入5个hello word
}
fclose(fp);
fp = NULL;
return 0;
}
将刚写入文件的hello word存储回buf 数组中
#include <stdio.h>
#include <string.h>
int main()
{
FILE * fp = fopen("word.txt", "r");
if(!fp) printf("文件打开失败\n");
char buf[12];
while (fread(buf, 1, strlen(str), fp)) {
buf[11] = 0;
printf("%s", buf);
}
fclose(fp);
fp = NULL;
return 0;
}
系统文件I/O
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码
open接口介绍
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项(创建文件的方式),
用下面的一个或者多个常量进行“或”运算,构成flags。
mode:指明创建属性
//创建文件的三种方式
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,
来指明新文件的访问权限
O_APPEND: 追加写(类似于c语言中的fopen()的”a“选项, 会追加信息)
返回值:
成功:新打开的文件描述符
失败:-1
flags参数:
grep -ER 'O_CREAT | O_APPEND | O_RDONLY)' /usr/include/
/usr/include/bits/fcntl-linux.h
//flags对应参数所在头文件
系统标准头文件中:/usr/include/bits/fcntl-linux.h
如果理解flags
int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
其实当我们写出这行代码的时候,会去调用系统调用open,open函数的第一个
参数代表的是要打开的目标文件,而 O_WRONLY | O_CREAT | O_APPEND
这是一个位运算表达式,而按位或(|)的结果值表示的是:需要使用open函数
打开目标文件后,执行的功能,其实每一个选项(O_WRONLY | O_CREAT | O_APPEND)他都有对应的一个值,这个值被包含在/usr/include/bits/fcntl-linux.h这个头文件中(也就是上述图)。
程序演示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
int fd = open("word.txt", O_RDONLY); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
close(fd); //关闭文件
return 0;
}
read文档:
read接口介绍:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
Read()尝试从文件描述符fd中读取字节数
从buf开始的缓冲区。
对于支持查找的文件,读操作从当前位置开始
租用文件偏移量,文件偏移量随数量递增
字节读。 如果当前文件的偏移量位于或超过文件的末尾,
不读取字节,read()返回零。
如果count为零,read()可能会检测到下面描述的错误。 在
没有任何错误,或者如果read()不检查错误,则read()
计数为0时返回0,没有其他效果。
如果count大于SSIZE_MAX,结果是不指定的。
程序演示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
int fd = open("word.txt", O_RDONLY); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
//文件打开成功
char buf[SIZE];
read(fd, buf ,SIZE); //从文件描述符fd中读取size个字节的字符到buf数组中。
printf("%s", buf); //输出buf数组中的字符串
close(fd); //关闭文件
return 0;
}
open函数返回值
在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图:
系统调用接口和库函数的关系,一目了然。所以,可以认为,在c语言中 f#系列的函数,都是对系统调用的封装,方便二次开发
文件描述符fd
代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
int fd = open("word.txt", O_RDONLY); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
//文件打开成功
char buf[SIZE];
read(fd, buf ,SIZE); //从文件fd中读取size个字节的字符到buf数组中。
printf("fileindex:%d , src: %s", fd, buf);
//打印fopen的返回值,以及buf数组中存放的字符串
close(fd); //关闭文件
return 0;
}
程序运行结果:
从程序的运行结果来看,确实当我们的程序运行起来之后,会将会打开
"word.txt"
文件,并在执行read函数的时候将"word.txt"
文件中的内容写入到buf这个字符数组中去,另外需要关心的是open在执行成功之后带回来一个val值为3的返回值。这是为什么呢?
为了一探究竟接下来我们可以写一个程序用来测试fd的返回值
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
int fd = open("word.txt", O_RDONLY); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
//读取文件的信息存放到buf数组中
char buf[SIZE];
read(fd, buf ,SIZE); //从文件fd中读取size个字节的字符到buf数组中。
printf("fileindex:%d , src: %s", fd, buf);
/* 使用open()系统调用打开多个文件,观察fd的返回值结果 */
int fd1 = open("word1.txt", O_WRONLY | O_CREAT); //打开文件只写,如果没有就创建
int fd2 = open("word2.txt", O_WRONLY | O_CREAT); //只写 。。。。。。。。。。
int fd3 = open("word3.txt", O_WRONLY | O_CREAT); //只写 。。。。。。。 。 。
int fd4 = open("word4.txt", O_WRONLY | O_CREAT); //只写 。。 。 。。 。 。。
int fd5 = open("word5.txt", O_WRONLY | O_CREAT); //只写 。 。。 。 。。 。。
/* 打印fd的返回值 */
printf("fd1:%d\n", fd1);
printf("fd2:%d\n", fd2);
printf("fd3:%d\n", fd3);
printf("fd4:%d\n", fd4);
printf("fd5:%d\n", fd5);
close(fd); //关闭文件
return 0;
}
程序运行结果:
现象1:从fd的返回值来看,他是连续打印的,fd每次的返回值也是顺序递增的,并且fd的返回值默认会从3开始打印
现象2:open("word1.txt", O_WRONLY | O_CREAT);
执行打开文件的时候,带了两个选项,而我们使用c语言的fopen打开文件的时候只需要带”w“
文件描述符的分配规则
疑问?为什么fd的返回值默认从3开始?
其实c语言的程序一旦执行起来就会默认打开三个IO流,分别是stdio、stdin、stderr标准输入输出流。而后面的3、4、5、6、7、8其实是文件描述符,文件描述符在系统层面就是一个整数,这个整数的取值范围是【0,n】,
那么之前的0~2数字其实依次代表的是:
0:标准输入(stdin) 1:标准输出(stdout) 2:标准错误(stderr)
其实【0,n】的数字本质上是数组的下标。
对于进程来讲,默认的文件描述符从3开始分配(因为0~2是被标准输入输出和stderr占用了)。
总结:
1、open是属于封装出来的系统调用接口
2、open函数的返回值是一个文件描述符fd
3、文件描述符有他的分配规则,站在进程的角度,文件描述符默认只会分配最小值。
使用程序证明, 如果stdin流被关闭了,那么返回的fd会是多少?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
close(0); //将标准输入流关闭
int fd = open("word.txt", O_RDONLY); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
/* 打印fd的返回值 */
printf("fd:%d\n", fd);
close(fd); //关闭文件
return 0;
}
程序运行结果:
结论:给文件描述符分配时,默认从最小开始分配。
重定向
有趣的实验:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256
//使用系统调用接口完成文件读写操作
int main()
{
close(1); //将标准输入流关闭
int fd = open("word.txt", O_WRONLY | O_CREAT); //只读打开
if(fd < 0) {
perror("文件打开失败\n");
return 0;
}
/* 打印fd的返回值 */
printf("hhhhhhh....%d\n", fd);
fflush(stdout);
close(fd); //关闭文件
return 0;
}
程序运行结果:
我们会发现原本应该应该打印到显示屏的hhhhhhh....1
却被输出到了文件中,那么为什么呢?其实一开始的标准输出流是被打开了的,可是我们却close关闭了stdout流,那么1这个文件标识符的值就会空着,而当使用open函数打开"word.txt"
文件时,系统就会将原本属于stdout的文件标识符1分配给"word.txt"
文件,所以导致原本应该输出到屏幕的值却输出到了文件"word.txt"
上,而这也是重定向
的原理
总结:
1、当fd等于1,这种现象也叫做重定向(常见的重定向有:>, >>, <)
2、重定向的本质其实是,当原本属于stdout的文件描述符1,被分配给了被open打开文件的fd,并在输出的过程将原本应该输出到stdout流的内容被重定向到了被open打开的文件
问题? 文件被打开了那么需不需要被管理?交给谁管理?
文件其实还是交给OS管理的,而OS管理的原则是:
先描述,再组织
,当文件被加载到内存中之后,操作系统会以某种数据结构的方式管理起文件,而PCB中有一个task_struct类型的结构体,里面会存放一个指针,该指针会指向一个指针数组,获得他的首地址。
那如果我们需要写入一些数据进入文件的话,操作系统会怎么做呢?
操作系统会使用task_struct中的数组指针通过获取struct file *arr[0]的地址,通过数组是线性存储的特性,通过下标索引找到对应的文件,再向该文件中写入数据
问题使用open()函数打开文件,返回的fd值又是谁的?
答案是指针数组的索引位置
下面的代码均摘自linux 2.6.24。
struct files_struct在<include/linux/fdtable.h>中定义如下:
struct files_struct {
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
如何理解linux下一切皆文件?
在我们实际使用硬盘、显示器、键盘的时候,不管是读取还是写入的过程其实调用的接口分别是:
(h_read()、h_write() )硬盘读写
(x_read()、x_write() )显示器读写
(j_read()、j_write() )键盘读写
其实struct file结构体中会包含open、write、read的函数指针,而驱动层也会有对应的几个函数接口,由于他们的参数是相同的,所以可以通过struct file结构体中的函数指针指向驱动层的函数接口,达到open、write、read可以分别使用硬盘、显示器、键盘。
(读者可以理解为是类似于多态,因为同一个函数可以实现不同的功能)
,由于操作系统站在上层的角度,所以并不需要关心底层的实现,操作系统真正关心的是使用了软件封装出来的虚拟层,所以在操作系统看来一切皆文件!
以下是该结构体 在内核2.6.5中看起来的样子:
struct file_operations {undefined
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
FILE
有趣实验:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main()
{
close(1);
int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd < 0){
printf("opend error\n");
return 1;
}
const char* str = "hello word!:write\n";
const char *str1 = "hello word!:printf\n";
const char *str2 = "hello word!:fprintf\n";
write(1, str, strlen(str));
printf(str1);
fprintf(stdout, str2);
fflush(stdout);
close(fd);
return 0;
}
//程序运行结果:
[mzt@VM-16-4-centos test]$ cat w.txt
hello word!:write
hello word!:printf
hello word!:fprintf
当我们将标准输出流关闭之后,我们原本需要输出到屏幕的信息全都被输出到了文件中,其实这个现象博主上面也解释过了。那么还有一些知识需要大家掌握。
-
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
-
所以C库当中的FILE结构体内部,必定封装了fd
-
其实struct FILE 结构体是被存放在系统头文件
libio.h
中的,而这个系统头文件的路径是:/usr/include/libio.h
。 -
FILE
其实只是struct _IO_FILE
结构体类型的别名
其实stdout也是属于FILE类型结构体的指针,既然是FILE类型,那么stdout中一定会包含_fileno
对比write(1, str, strlen(str)) 这句代码能够向文件中写入可以理解,但是为什么 fprintf(stdout, str2);这句代码也能向文件中写入呢?因为stdout中存放的_fileno中的值并没有被改变他还是1!所以这两个代码的功能是一样的!
当我们看完他的相同点之后,再来看他的不同点,这里还是用一段小程序测试:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
int main()
{
close(1);
int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd < 0){
printf("opend error\n");
return 1;
}
const char* str = "hello word!:write\n";
const char *str1 = "hello word!:printf\n";
const char *str2 = "hello word!:fprintf\n";
write(1, str, strlen(str));
printf(str1);
fprintf(stdout, str2);
fork();
fflush(stdout);
close(fd);
return 0;
}
从程序的运行结果我们不难发现,write
只被执行了一次,而printf
和fprintf
却被执行了两次
write和printf/fprintf他们之间的区别就是write是系统调用接口,而printf / fprintf确实库函数,其实向显示器上输出结果这种方式也是有缓冲区的,这种缓冲方式是
行缓冲(遇到\n就刷新,因为需要被立马看到)
,而如果是往文件中输出内容并不需要被用户立马就看见,所以文件是以全缓冲(缓冲区存不下了才会往硬盘中的文件输出)
而这个重定向其实引起来的最大的变化是:在重定向的时候,我们写入数据的目的地发生了变化,没有重定向本身是要往显示器上写,而重定向之后就必须要往文件中写,所以一旦重定向之后是影响缓冲方式的!!
我们发现 printf 和 fprintf (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork执行后的fflush(stdout);有关!
- 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
- printf fprintf 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
- 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
- 但是进程退出之后,会统一刷新,写入文件当中。
- 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
- write 没有变化,说明没有所谓的缓冲。
综上:
printf fwrite 库函数会自带缓冲区,而 write系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢?printf fwrite是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
使用dup2系统调用
Dup2()使newfd成为oldfd的副本(使用oldfd拷贝出newfd),必要时先关闭newfd‐sary。
dup2原理:
如果想要stdout(标准输出流)打印到屏幕的信息写入文件当中,完成标准输出重定向到文件中的操作,我们需要将指向stdou这个文件的指针去指向myfile这个被创建出来的file对象就行,中间的过程是将index位置为3的数组中的指针拷贝给index下标位置为1中,那么就会让index位置1处的*file去指向新打开的myfile文件所对应的对象(也就是所谓的oldfd拷贝给newfd,那么index位置为1处的指针就会被更新为指向myfile文件所关联的file对象)
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
int main()
{
//close(1);
int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd < 0){
printf("opend error\n");
return 1;
}
dup2(fd, 1);
//将位于指针数组中index位置为fd的指针,拷贝给index位置为1处的指针
const char* str = "hello word!:write\n";
const char *str1 = "hello word!:printf\n";
const char *str2 = "hello word!:fprintf\n";
write(1, str, strlen(str));
printf(str1);
fprintf(stdout, str2);
fork();
fflush(stdout);
close(fd); //文件描述符是有一定的上限的,需要关闭节省资源
return 0;
}
理解文件系统
我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据
每行包含7列:
- 模式
- 硬链接数
- 文件所有者
- 组
- 大小
- 最后修改时间
- 文件名
ls -l读取存储在磁盘上的文件信息,其实本质上是将文件的信息放到内存,然后显示出来。
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
上面的执行结果有几个信息需要解释清楚
inode
为了能解释清楚inode我们先简单了解一下文件系统
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。
Boot Block其实是一个启动块,启动块一般是第一个物理盘的扇区,这个启动块会包含硬盘的分区表这样的信息, 并且这个分区也会指明你的操作系统盘在哪个分区,并且他能找到操作系统
- Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
- 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,注意:Super Block 结构在每一个块组里面都有一个Super Block,并且是一样的,这样做的目的是为了防止Super Block 被破快导致剩下的空间块无法使用,
- GDT,Group Descriptor Table:块组描述符,描述块组属性信息,并且里面记录了inode的使用情况和inode的总个数。
- 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用(记录inode哪些被使用,哪些没有被使用)
- inode位图(inode Bitmap):inode Bitmap它其实是一个位图,他是利用了一种哈希的原理将每个bit表示一个inode是否空闲可用。
- i节点表(inode table):存放文件属性 如 文件大小,所有者,最近修改时间等, 其中inode:存放文件的属性, inode必须包含data blocks对应的映射关系,inode id会 标识一个inode
- 数据区(data blocks):存放文件内容
- 文件和inode的关系是1对1,文件和data blocks的关系是1对n
文件创建、删除、查找过程
如何创建一个文件?
- 其实创建一个文件的过程包含以下:先给文件分配inode,但是需要在inode Bitmap该表中找一个没有被分配的比特位,它代表的是一个inode,分配成功后会将它置为1,而文件的属性会包含
大小、最后修改时间、文件名.
所以需要将文件的属性填入inode table,通过inode table和data blocks所对应的映射关系将数据写入到这个数据块中。
如何查找一个文价?
- 先找到文件所在的块组,在inode table中找到对应的inode信息,找到inode对应的信息就能够找到属性,根据他的块组映射找到对应的数据块,将数据提取出来
如何删除一个文件?
- 将需要删除的文件所对应的inode编号在位图inode Bitmap当中,将1置为0。
如何恢复一个文件?
- 根据inode将对应的inode Bitmap位置置为1,再根据inode table和data blocks所对应的映射关系置为1,这个文件就被恢复了。
将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。
我们将上图简化:
创建一个新文件主要有一下4个操作:
- 存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。 - 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据
复制到300,下一块复制到500,以此类推。 - 记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。 - 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文
件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来
问题? 目录算是文件吗?
是,目录也是由inode和数据块构成的,而一个数据块中保存的其实是文件名和inode id,所以目录中的数据存放的是文件名到inode的映射关系。
理解硬链接
了解硬链接:
[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -1i
abc def 263466 abc 263466 def
我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个
- abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466 的硬连接数为2。
- 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。
软链接
具有独立的inode,因为log.txt和log.txt的软链接都是只有一个inode,既然各自都有一个inode那么它就是一个独立的文件,
硬链接
其实硬链接h_link和log. txt文件共享的是同一个inode,所以硬链接并不是一个独立的文件,那么硬链接是什么? 其实h_link在本质上还是一个文件名,只是它使用的是log. txt的inode编码,其实test这个当前目录中也是会存放文件名和inode的映射关系,而h_link只是一个特定文件名和inode的映射关系,
为什么目录的链接数是2?其实文件名dir和1835039( inode id )属于一组映射关系,还有的就是dir目录中的隐藏文件 . 会和1835039属于一组映射关系,所以dir的连接数是2
但是dir中也会存放当前目录和上级目录,如果dir的连接数为3,那么dir和1835039属于一组映射关系,而dir中的隐藏文件 .
和1835039属于一组映射关系,tmp中的隐藏文件. .
和1835039属于一组映射关系。所以dir中就会有3个连接数