一、内存管理的层次关系
用户层 | ||
---|---|---|
STL | 自动分配、自动释放 | 调用C++ |
C++ | new /delete 、构造/析构 |
调用C |
C | malloc \ calloc \ realloc \ free |
调用POSIX\Linux |
POSIX | sbrk \ brk |
调用Kernal |
Linux | mmap \ munmap |
调用Kernal |
系统层 | ||
Kernal | kmalloc \ vmalloc |
调用驱动Driver |
Driver | get_free_page |
... |
二、进程映像
程序与进程:
- 程序是存储在磁盘上的可执行文件,当程序被运行时,系统会把程序从磁盘加载到内存中运行,正在运行中的程序称为进程,一个程序可以同时被加载多次,形成多个进程,每个进程相互独立,由操作系统管理
进程映像:进程在内存空间中的分布使用情况就称为进程映像,从低地址到高地址分别是:
- 代码段
text
- 存储二进制指令、字面值常量、被
const
修饰过的原data
段的数据 - 权限
r--
或者r-x
权限只读
- 存储二进制指令、字面值常量、被
- 数据段
data
- 存储初始化过的全局变量和静态变量
- 静态数据段
BSS
- 存储未初始化过的全局变量和静态变量
- 进程一旦加载,此内存段会被操作系统自动清零,默认值是0
- 如果初始化的值给0,依然还在BSS
- 堆区
heap
- 要程序员动态分配、动态释放,从低地址向高地址扩招
- 使用
malloc
系列函数进行内存管理 - 而
malloc
系列函数底层调用操作系统的API(brk
\sbrk
\mmap
\munmap
)
- 栈区
stack
- 存储非静态局部变量、块变量,包括函数的参数(除了main函数的参数)、返回值
- 内存扩展从高地址向低地址扩展
- 栈区与堆区中间有一段预留空间,一个作用为了预留,第二个是让共享库的内存以及共享内存使用此段内存
- 命令行参数与环境变量表
- 里面存储环境变量表以及命令行传给main的参数内容
一、系统调用(系统API)
什么是系统调用
- 由操作系统向应用程序提供的程序接口信息,本质上就是应用程序与操作系统之间交互的接口。
- 操作系统的主要功能是为了管理硬件资源和为应用软件的开发人员提供一个良好的环境,使得应用程序具有更好的兼容性,为了达到这个目的,内核提供一套统一的具有一定功能的内核接口函数,称为系统调用\系统函数,以C语言函数的格式提供给用户
- 操作系统负责把应用程序的请求传给内核,由内核调用具体内核功能完成所需请求,结束后把结果通过操作系统的接口函数的返回值传递给调用者
- UNIX\Linux大部分系统中的系统功能都是通过系统调用来实现的,相当于调用系统函数,但是它们不是真正意义的函数,当调用它们时,进程立即进入内核态,当调用结束,会重新转入回用户态
time ./<可执行文件名> # 统计该进程的时间分布
real 0m0.003s # 总时间
user 0m0.001s # 用户态执行时间
sys 0m0.000s # 内核态执行之间
总时间 = 用户态 + 内核态 + 用户态\内核态切换耗时 + 休眠时间
strace ./可执行文件名 # 追踪函数的底层调用过程,以此了解标准库函数中用了哪些系统调用
普通函数与系统函数(系统调用)的区别:
普通函数的调用步骤:
- 调用者会把要传递的参数压入栈内存
- 根据函数名也就是该函数的代码段地址,跳转到该函数代码段中执行
- 从栈内存中弹出传递的参数数据
- 定义的相关局部变量会入栈到该函数的栈内存进行扩展,并执行相关代码
- 返回执行结果
- 销毁该函数的栈内存
- 返回调用语句处继续执行
系统函数的调用过程:
- 当执行到系统函数的位置时,会触发软件中断机制
- 然后进程会转入内核态执行,由内核负责把参数从用户空间拷贝到内核空间
- 然后内核根据中断编号来执行相应的操作
- 等执行完毕后,内核再把执行结果从内核空间再拷贝回用户空间中
- 返回到中断触发位置,转换回用户态继续执行进程
二、一切皆文件
- 在UNIX\Linux系统中,操作系统把所有服务、设备都抽象成了文件,因为这样可以给各种设备、服务提供同一套统一而简单的操作接口,程序就可以像访问普通文件一样,控制串口、网络、打印机等设备
- 因此在UNIX\Linux系统中对文件就具有特别重要的意义,一切皆文件,所以大多数情况下,只需要五个基本系统函数操作 open/close/read/write/ioctl,既可以完成对各种设备的输入、输出控制
文件分类:
- 普通文件 - 包括文本文件、二进制文件、各种压缩包文件等
- 目录文件 d 类似Windows的文件夹
- 块设备文件 b 用于保存大块数据的设备,例如磁盘
- 字符设备文件 c 用于对字符处理的服务、设备,例如 键盘
- 链接文件 l 类似于Windows的快捷方式
- 管道文件 p 用于早期进程通信
- Socket文件\套接字文件 s 用于网络通信
三、文件描述符
什么是文件描述符:
- 文件描述符是一种非负的整数,用于表示一个打开了的文件
- 由系统调用(open\creat)返回值,在后续操作文件时可以被内核空间引用去操作对应的文件
- 它代表了一个内核对象(类似于FILE*),因为内核不能暴露它的内存地址,因此不能返回真正的文件地址给用户
- 内核中有一张表格,记录了所有被打开的文件对象,文件描述符就是访问这张表格的下标,文件描述符也叫做句柄,是用户操作文件的凭证
- 内核只要启动,一定会给每个进程打开三个文件描述符,并且是一直默认打开,除非自己手动关闭
// 在<unistd.h> 定义了三个宏
#define STDIN_FILENO 0 // 标准输入 文件指针 stdin
#define STDOUT_FILENO 1 // 标准输出 文件指针 stdout
#define STDERR_FILENO 2 /* 标准输入 文件指针 stderr
文件描述符与文件指针:
- 在Linux系统中打开文件后,内存中(内核区间)就会有一个内核对象,也就是记录了该文件相关信息的结构变量,但是内核为了自己的安全不能把它的地址返回给用户,而且内核区间用户是无法直接访问的,就算返回也无权限访问。
- 而且一个进程可以同时打开多份文件,所以操作系统会在内核区间创造一份索引表,表中的每一项都指向了一个打开的文件内核对象,而用户拿到的文件描述符就是该索引表的下标(主键),因此不同进程之间直接交互文件描述符是没有意义的。
- C语言标准函数中,使用文件指针代表文件,文件指针指向的区间是进程的用户 区间的一个结构变量(文件结构变量FILE类型),里面记录了该文件的文件描述符还有一些文件缓冲区,因此某种程度上可以理解文件指针就是文件描述符,只是不同的形式,给不同的对象使用而已
int fileno(FILE *stream);
功能:把文件指针转换成文件描述符
FILE *fdopen(int fd, const char *mode);
功能:把文件描述符转换成文件指针
四、文件的创建与打开
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
功能:打开文件
pathname:文件路径
flags:打开文件的方式
返回值:文件描述符
int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
pathname:文件路径
flags:打开文件的方式
mode:创建文件时的权限
返回值:成功返回文件描述符,失败返回负数
注意:open\creat所返回的一定是当前未使用过的最小的文件描述符
注意:一个进程可以同时打开多个文件描述符,最大的数量受limit.h中宏控制数量,在不同的系统标准中不一样,POSIX中不低于16,传统UNIX63个,现代的Linux系统255个
int creat(const char *pathname, mode_t mode);
功能:专门用于创建文件,但是基本不用,因为open可以覆盖它的功能
flags:
O_RDONLY 只读权限
O_WRONLY 只写权限
O_RDWR 读写权限
O_APPEND 打开文件后位置指针指向末尾
O_CREAT 文件不存在会创建
O_EXCL 如果文件存在则创建失败,如果没有该flags,文件存在直接打开
O_TRUNC 清空内容打开
O_ASYNC 当文件描述符可读/可写时,会向调用进程发送信号SIGIO
mode: 提供一个三位八进制数表示权限,当flags为O_CREAT时必须提供
宏名 权限码
S_IRWXU 00700 用户权限码
S_IRUSR 00400 读权限
S_IWUSR 00200 写权限
S_IXUSR 00100 执行权限
S_IRWXG 00070 同组其它用户权限
S_IRGRP 00040
S_IWGRP 00020
S_IXGRP 00010
S_IRWXO 00007 除了同组的用户外,其它用户的权限
S_IROTH 00004
S_IWOTH 00002
S_IXOTH 00001
#include <unistd.h>
int close(int fd);
功能:关闭文件,成功0 失败-1
问题1:C语言可不可以定义重名函数?
可以,但是需要在不同作用域下才可以重名,同一作用域下不可以
情况1:在不同的源文件中,static声明的函数可以与其他源文件中的普通函数重名
情况2:在函数内定义的函数可以与普通函数重名
问题2: 系统调用为什么可以重名?
因为系统调用本身就不是真正的函数,而是借助了软中断实现内核执行对应的系统操作,而决定执行哪个系统调用操作是由中断编号决定的,而不是名字
问题3:标准库函数中的 r \ r+ \ w \ w+ \ a \ a+ 分别对应系统调用中的flags的哪些标志?
strace ./a.out
r O_RDONLY
r+ O_RDWR
w O_WRONLY|O_CREAT|O_TRUNC 0666
w+ O_RDWR|O_CREAT|O_TRUNC 0666
a O_WRONLY|O_CREAT|O_APPEND, 0666
a+ O_RDWR|O_CREAT|O_APPEND, 0666
五、文件读写
ssize_t write(int fd, const void *buf, size_t count);
功能:写入文件内容
fd:文件描述符
buf:要写入的数据的内存首地址
count:要写入的字节数
返回值:成功写入的字节数
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
fd:文件描述符
buf:存储读取到的数据的内存首地址
count:想要读取的字节数,一般就是buf的大小
返回值:成功读取到的字节数
注意:它们与标准C的 fwrite/fread 很像,但是它们更纯粹直接
练习1:以二进制形式写入10000000个整数到文件中,分别使用标准IO和系统IO来完成,比较它们的速度谁更快,为什么?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
void std_io(void)
{
FILE* fp = fopen("test.txt","w");
if(NULL == fp)
{
perror("fopen");
return;
}
for(int i=0; i<10000000; i++)
{
int num = rand();
fwrite(&num,sizeof(num),1,fp);
}
fclose(fp);
}
void sys_io(void)
{
int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(0 > fd)
{
perror("open");
return;
}
for(int i=0; i<10000000; i++)
{
int num = rand();
write(fd,&num,sizeof(num));
}
close(fd);
}
int main(int argc,const char* argv[])
{
//std_io();
sys_io();
}
六、系统IO与标准IO
- 当系统调用被执行时,需要从用户态转换成内核态,执行完毕后又要转回用户态,如果频繁来回切换会导致性能丢失
- 在标准IO中,内部维护一个缓冲区(1k,1024字节),要写入的数据先存储到缓冲区中,只有当满足特定条件时才会把缓冲区中数据全部通过系统调用write写入文件,从而降低了系统调用的使用频率,减少了状态的切换,因此标准IO的效率要比直接使用系统IO要快
- 如果想要提高系统IO的速度,可以尝试自己维护一个缓冲区,先把数据存储到缓冲区,等满后再调用write,这样提高速度
- 普通情况下建议使用标准IO,因为更快,如果对速度还有更高的要求,可以自己使用系统IO+大缓冲区
void sys_io(void)
{
int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(0 > fd)
{
perror("open");
return;
}
int buf[1024] = {};
for(int i=0; i<10000000; i++)
{
int num = rand();
buf[i%1024] = num;
if(0 == (i+1)%1024)
{
write(fd,buf,sizeof(buf));
}
}
close(fd);
}
- UNIX/Linux只有一套读写文件的系统调用,没有像标准C中的文本读写 ,那么可以先把数据转换成字符串(sprintf/sscanf),然后再通过write\read读写,从而实现文本读写的效果
作业1:使用系统IO实现一个带有覆盖检查的cp命令 ./CP a b
七、文件位置指针
- 与标准IO的文件读写位置指针一样,系统IO时也会有一个表示位置的指针在移动,会随着读写操作的执行向后自动移动
- 当需要随机位置进行读写操作时,那么需要移动位置指针的位置
off_t lseek(int fd, off_t offset, int whence);
功能:调整文件指针的位置
fd:要调整的文件描述符
offset:
偏移值
whence:基础位置
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾
返回值:返回当前位置指针的位置 从文件开头计算该位置的字节数
注意:系统IO中不需要ftell函数的系统调用,因为lseek就能够完成获取位置指针位置的功能
- 当超过文件末尾的位置再写入数据时,原末尾与最后的数据之间会有一个“数据黑洞”,但是黑洞不占用磁盘大小,但是会计算成文件的大小,不会影响后序的读写
八、文件同步
- 大多数磁盘I/O都有缓冲区机制,写入文件其实先写入缓冲区,直到缓冲区满才将其排入写队列。降低写操作的次数,提高写操作效率,但是可能会导致磁盘文件与缓冲区数据不同步,可以借助系统调用来强制让磁盘与缓冲区的内容同步:
void sync(void);
功能:将所有被修改过的缓冲区中的数据排入写队列,立即返回,不等待写入磁盘的完成
int fsync(int fd);
功能:只针对文件fd,并且会等待写入完成才返回
int fdatasync(int fd);
功能:只同步文件fd的数据,不同步文件属性,等待写入完成才返回
九、文件描述符的状态标志
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置或获取文件描述符的状态标志
cmd:
F_GETFD 获取文件描述符状态标志
F_SETFD 设置文件描述符状态标志
F_GETFL 获取文件状态标志
其中 O_CREAT\O_EXCL\O_TRUNC 获取不了
F_SETFL 追加文件状态标志
十、文件锁
文件锁的意义:
- 当多个进程同时访问同一个文件时,就有可能造成文件的数据读写混乱,为了解决这一问题可以在读写文件前给文件尝试并加锁
文件锁的限制:
- 一般情况下,系统提供的文件锁都是劝解锁,内核主要负责文件的加锁和检查是否上锁,而不直接参与锁的控制以及协同操作,这类锁就需要程序员每次用之前都要检查是否被别的进程加锁,再实现并发操作
int fcntl(int fd, int cmd, flock* lock );
功能:对文件的某一部分进行锁操作
struct flock {
short l_type; /* 锁的类型
F_RDLCK,读锁
F_WRLCK, 写锁
F_UNLCK 解锁*/
short l_whence; /* 偏移起点
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 偏移值 锁区起始位置:l_whence+l_start */
off_t l_len; /* 锁区长度 0表示锁到文件末尾*/
pid_t l_pid; /* 加锁的进程id -1表示让内核自动设置 */
};
cmd可选:
F_GETLK 测试lock所表示的锁能否加锁
如果可以加则讲lock.l_type设置为F_UNLCK
否则会通过lock.l_type返回当前锁信息
F_SETLK 设置文件的锁定状态为lock.l_type
成功返回0,失败返回-1,如果有其他进程持有该锁导致加锁失败,返回EACCES or EAGAIN
F_SETLKW 设置文件的锁定状态为lock.l_type
成功返回0,否则一直等待,除非被其它信号打断返回-1
进程A:读锁 进程B:读锁 可共享
进程A:写锁 进程B:读锁 互斥
进程A:读锁 进程B:写锁 互斥
进程A:写锁 进程B:写锁 互斥
十一、复制文件描述符
int dup(int oldfd);
功能:复制文件描述符
oldfd:已经打开的要复制的文件描述符
返回值:返回一个新的文件描述符,是当前可用的文件描述符中最小值,失败-1
int dup2(int oldfd, int newfd);
功能:复制oldfd文件描述符成指定的文件描述符newfd
如果newfd原来已经被占用,则会把它关闭重新复制
返回值:成功0 失败-1
注意:复制成功后,相当于两个文件描述符对应同一个打开的文件
复制文件描述符的意义:
- 复制成功后,相当于两个文件描述符对应同一个打开的文件,可以以此实现很多奇特的操作,例如重定向文件读写、重定向命令 ls >> file
- 通过把一个已经打开了的文件描述符fd,通过dup2重定向为标准输入0或者标准输出1,此时就会先把0、1文件关闭,然后0\1指向我们刚刚的文件fd,此后,通过输出语句执行时,相当于把本来要输出到屏幕的内容,直接写如到文件fd中,相当于执行write,如果执行输入语句,相当于把本来要从键盘中输入的内容,直接从文件fd中读取,相当于执行了read操作
十二、获取文件属性
int stat(const char *pathname, struct stat *buf);
功能:根据文件路径获取该文件的属性
int fstat(int fd, struct stat *buf);
功能:根据文件描述符获取该文件的属性
int lstat(const char *pathname, struct stat *buf);
功能:根据文件路径获取链接文件的属性
struct stat {
dev_t st_dev; /* 文件的设备ID*/
ino_t st_ino; /* 文件的inode节点号*/
mode_t st_mode; /* 文件的类型和权限 */
nlink_t st_nlink; /* 硬链接数量 */
uid_t st_uid; /* 属主ID */
gid_t st_gid; /* 属组ID */
dev_t st_rdev; /* 特殊设备ID */
off_t st_size; /* 文件的总字节数 */
blksize_t st_blksize; /* 文件的IO块数量*/
blkcnt_t st_blocks; /* 以512字节为一块,该文件占了几块*/
struct timespec st_atim; /* 最后访问时间*/
struct timespec st_mtim; /* 最后内容修改时间*/
struct timespec st_ctim; /* 最后文件状态属性修改时间*/
#define st_atime st_atim.tv_sec 时间换算成总秒数
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
st_mode 记录了文件类型和权限
S_IFMT 0170000 获取文件类型的掩码
S_IFSOCK 0140000 socket文件
S_IFLNK 0120000 软链接文件
S_IFREG 0100000 普通文件
S_IFBLK 0060000 块设备文件
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符设备文件
S_IFIFO 0010000 FIFO 管道文件
/*
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {
/* Handle regular file */}*/
或者借助提供的宏函数来判断文件类型:
S_ISREG(m) 是否是普通文件
S_ISDIR(m) 目录
S_ISCHR(m) 字符设备文件
S_ISBLK(m) 块设备文件
S_ISFIFO(m) 管道文件
S_ISLNK(m) 软链接文件
S_ISSOCK(m) socket文件
判断权限:
S_IRWXU 00700 判断属主的读写执行权限的权限码
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 判断属组其他用户的读写执行权限的权限码
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 判断其它用户的读写执行权限的权限码
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
// 显示文件类型和权限
const char* mtos(mode_t m)
{
static char s[11];
if(S_ISREG(m))
strcpy(s,"-");
else if(S_ISDIR(m))
strcpy(s,"d");
else if(S_ISCHR(m))
strcpy(s,"c");
else if(S_ISBLK(m))
strcpy(s,"b");
else if(S_ISFIFO(m))
strcpy(s,"p");
else if(S_ISLNK(m))
strcpy(s,"l");
else if(S_ISSOCK(m))
strcpy(s,"s");
strcat(s,m & S_IRUSR ? "r":"-");
strcat(s,m & S_IWUSR ? "w":"-");
strcat(s,m & S_IXUSR ? "x":"-");
strcat(s,m & S_IRGRP ? "r":"-");
strcat(s,m & S_IWGRP ? "w":"-");
strcat(s,m & S_IXGRP ? "x":"-");
strcat(s,m & S_IROTH ? "r":"-");
strcat(s,m & S_IWOTH ? "w":"-");
strcat(s,m & S_IXOTH ? "x":"-");
return s;
}
const char* ttos(time_t t)
{
static char s[20];
struct tm* time = localtime(&t);
sprintf(s,"%04d-%02d-%02d %02d:%02d:%02d",
time->tm_year+1900,
time->tm_mon+1,
time->tm_mday,
time->tm_hour,
time->tm_min,
time->tm_sec);
return s;
}
int main(int argc,const char* argv[])
{
if(2 > argc)
{
printf("User:./a.out <path>\n");
return 0;
}
struct stat st;
if(-1 == stat(argv[1],&st))
{
perror("stat");
return -1;
}
printf("设备ID:%llu\n",st.st_dev);
printf("inode节点号:%lu\n",st.st_ino);
printf("文件类型:%s\n",mtos(st.st_mode));
printf("最后访问时间:%s\n",ttos(st.st_atime));
printf("最后修改时间:%s\n",ttos(st.st_mtime));
printf("最后状态修改时间:%s\n",ttos(st.st_ctime));
}
十三、文件的权限
测试文件的权限:
int access(const char *pathname, int mode);
功能:测试当前用户对该文件的权限
pathname:要测试的文件路径
mode:
R_OK 读权限
W_OK 写权限
X_OK 执行权限
F_OK 测试文件是否存在
返回值:存在权限返回0 否则返回-1
修改文件权限:
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
功能:修改文件的权限为mode,mode可以使用提供的宏,或者直接使用一个八进制数表示三组权限
注意:权限都可以修改
文件的权限屏蔽码:
- 当使用open\creat 创建文件时,无论给什么权限创建都会成功,但是系统中记录有一个权限屏蔽码会对用户创建的文件的权限进行过滤屏蔽,最终创建出来的文件权限要除去文件屏蔽码
// 可以通过命令 umask 查看当前用户的权限屏蔽码
// 可以通过命令 umask 0xxx 修改当前终端这一次的权限屏蔽码为0xxx
mode_t umask(mode_t mask);
功能:给当前进程设置权限屏蔽码
mask:新的屏蔽码
返回值:旧的屏蔽码
注意:只对当前进程有效
注意:屏蔽码只影响open、creat ,对于chmod、fchmod 不受影响
十四、修改文件大小
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
length:想要修改成的文件字节数
成功返回0,失败返回-1
截短末尾丢弃,加长末尾添0
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
int fd = open("trunc.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
if(-1 == fd)
{
perror("open");
return -1;
}
// 要映射新文件前,需要让文件大小超过0 才能映射并输入、输出
if(-1 == ftruncate(fd,100))
{
perror("ftruncate");
close(fd);
return -1;
}
char* str = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_FILE,fd,0);
if((void*)-1 == str)
{
perror("mmap");
return 0;
}
// 往映射后的虚拟内存写入数据
//printf("----%s\n",str);
sprintf(str,"hello worldxxxxxxxxxxiid\n");
printf("----%s\n",str);
// str[0] = 'a';
// 取消映射
munmap(str,100);
close(fd);
}
作业:实现一个函数,可以删除文件的[n,m]字节的内容
十五、链接文件
Linux的文件系统会把磁盘分区成主要的两大部分
- inode信息块
- 默认128B,里面主要记录文件的权限、大小、所有者、修改时间等基本信息
- block数据块
- 默认4Kb,记录了文件名和真正的文件数据内容
- 每个文件必须拥有一个唯一的inode以及若干个block组成,读取文件需要借助文件所在目录的block中记录的文件inode号,找到该文件的inode,inode中记录了该文件的block位置,从而最终读取文件
什么是软、硬链接文件?
硬链接文件
- 硬链接文件没有自己inode和block,只是在不同的目录下复制了一份源文件的inode信息,可以通过该inode找到同一份源文件的block
软链接文件
- 软链接文件会创建自己的新的inode和block,它的inode也是为了找到自己的block,而在它的block中存储的是链接源文件的文件名和inode信息
区别
- 删除源文件,只是删除源文件的inode块,但是硬链接文件不受影响,而软链接文件就无法访问了
- 当一个文件的硬链接数删除成0时,文件才被真正的删除
- 修改硬链接文件内容,源文件也会被修改;而修改软链接的block,不会改变源文件的内容,反而会让软链接无法找到源文件
- 硬链接不能链接目录,软链接可以
硬链接文件的创建和删除:
int link(const char *oldpath, const char *newpath);
功能:创建硬链接文件
与命令 link \ ln 功能一样
int unlink(const char *pathname);
功能:删除文件的硬链接,文件的硬链接数-1
int remove(const char *pathname);
功能:与unlink一致,都可以删除普通文件以及硬链接文件
注意:如果删除的文件正在被打开,则会等待文件关闭后删除
注意:如果删除的是软链接文件,则会只删除软链接文件本身,而不会对源文件有任何影响,而且没有任何一个可以借助软链接来删除链接对象文件的函数
软链接文件的创建与读取:
int symlink(const char *target, const char *linkpath);
功能:创建软链接文件
对应命令:ln -s
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:获取到软链接文件自身的block内容,也就是它链接对象的文件名,而不会获取到链接对象的文件内容
如果想要读取链接对象的文件内容,还是通过read\write进行
十六、工作目录
- 工作目录指的是当前进程所在的目录,它是相对路径的起点,在操作文件时,如果没有提供文件的绝对路径信息,那么会操作工作目录下的文件,一般默认下工作目录就是当前进程的目录
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作路径,相当于命令 pwd
int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd
十七、创建、删除、读取目录
int mkdir(const char *pathname, mode_t mode);
功能:创建空白目录
mode:目录的权限,目录必须有执行权限次才能进入
int rmdir(const char *pathname);
功能:删除目录,只能删除空白目录
int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
功能:打开目录文件
返回值:成功返回目录流指针,失败返回NULL
struct dirent *readdir(DIR *dirp);
功能:从目录流对象中读取一条条目信息
注意:读取完一条条目后,会自动的往后移动,只需要再次调用该函数,即可以读下一条目录
返回值:返回该条目录信息的结构指针或者NULL(读取失败或者读到了目录末尾结束)
结构体 struct dirent里面存储了目录中某个文件的信息
struct dirent {
ino_t d_ino; /* inode节点号*/
off_t d_off; /* 下一条条目的偏移量 注意是磁盘偏移量,而非内存地址*/
unsigned short d_reclen; /* 当前条目的长度 */
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
d_type的取值:
DT_BLK 块设备文件
DT_CHR 字符设备文件
DT_DIR 目录
DT_FIFO 管道文件
DT_LNK 软链接文件
DT_REG 普通文件
DT_SOCK socket文件
DT_UNKNOWN 未知
void rewinddir(DIR *dirp);
功能:复位目录流,设置目录流的位置指针回到开头
long telldir(DIR *dirp);
功能:获取当前目录流的位置
void seekdir(DIR *dirp, long loc);
功能:设置目录流的读取位置,这样可以进行任意条目的获取
int closedir(DIR *dirp);
功能:关闭目录文件
作业2:实现 ls -al 命令的功能
作业3:实现 rm -rf 命令 (建议:先备份虚拟机文件 用户名.vdi)
gcc xxx.c -o RM
RM filename 非空目录也可以删除
标签:文件,操作系统,int,st,描述符,fd,内存,权限 From: https://www.cnblogs.com/sleeeeeping/p/18364643