Linux文件、目录的基本操作
文件基本概念
文件的属性
文件名:由创建文件的用户决定我文件名,主要是为了方便用户找到文件,同一目录下不允许有重名文件。
标识符:一个系统内的各文件标识符唯一,对用户来说毫无可读性,因此标识符只是操作系统哟用于区分各个文件的一种内部名称。
类型:指明文件的类型。
位置:文件存放的路径(让用户使用)、在外存中的地址(操作系统使用,对有用不可见)。
大小:指明文件的大小。
保护信息:对文件进行保护的访问控制信息
文件的相关概念
操作系统应向上提供哪些功能 { 创建文件( c r e a t e 系统调用) 删除文件( d e l e t e 系统调用) 读文件( r e a d 系统调用) 写文件( w r i t e 系统调用) 打开文件( o p e n 系统调用) 关闭文件( c l o s e 系统调用) 操作系统应向上提供哪些功能\begin{cases} 创建文件 (create 系统调用)\\\\ 删除文件 (delete 系统调用)\\\\ 读文件 (read 系统调用)\\\\ 写文件 (write 系统调用)\\\\ 打开文件 (open 系统调用)\\\\ 关闭文件 (close 系统调用)\end{cases} 操作系统应向上提供哪些功能⎩ ⎨ ⎧创建文件(create系统调用)删除文件(delete系统调用)读文件(read系统调用)写文件(write系统调用)打开文件(open系统调用)关闭文件(close系统调用)
其他需要由操作系统实现的文件管理功能
文件共享:使多个用户可以共享使用一个文件
文件保护:如何保证不同的用户对文件有不同的操作权限
文件的逻辑结构
文件的逻辑结构 { 无结构文件 有结构文件 { 顺序文件 索引文件 索引顺序文件 文件的逻辑结构\begin{cases} 无结构文件 \\ \\ 有结构文件 \begin{cases} 顺序文件 \\ \\ 索引文件 \\\\ 索引顺序文件 \end{cases} \end{cases} 文件的逻辑结构⎩ ⎨ ⎧无结构文件有结构文件⎩ ⎨ ⎧顺序文件索引文件索引顺序文件
无结构文件:文件内部的数据就是一系列二进制流或字符流组成。又称 “流式文件”。eg. Windows中的.txt文件。
有结构文件:由一组相似的记录组成,又称 “记录式文件”。每条记录有若干个数据项组成。eg. 数据库表文件。一般来说,每条记录有一个数据项可作为关键字(作为识别不同记录的ID)
根据各条记录的长度(占用的存储空间)是否相等,又可分为定长记录和可变长记录两种。
顺序文件
顺序文件:文件中的记录一个接一个地顺序排列(逻辑上),记录可以是定长的或可变长的。各个记录在物理上可以顺序存储或链式存储
顺序存储(类似顺序表)
链式存储(类似链表)
顺序文件 { 链式存储 { 无论是定长 / 可变长记录,都无法实随机存取,每次智能从第一个记录开始依次往后查找 顺序存储 { 可变长记录 { 无法实现随机存取。每次只能从第一个记录开始依次往后查找 定长记录 { 可实现随机存取。记录长度为 L ,则第 i 个记录存放的相对位置为 i ∗ L 若采用串结构,无法快速找到某个关键字对应的记录 若采用顺序结构可以快速找到某关键字对应的记录(如折半查找) 顺序文件\begin{cases} 链式存储 \begin{cases} 无论是定长/可变长记录,都无法实随机存取,每次智能从第一个记录开始依次往后查找 \end{cases} \\\\ 顺序存储 \begin{cases} 可变长记录 \begin{cases}无法实现随机存取。每次只能从第一个记录开始依次往后查找 \end{cases} \\\\ 定长记录 \begin{cases} 可实现随机存取。记录长度为L,则第i个记录存放的相对位置为i*L \\\\ 若采用串结构,无法快速找到某个关键字对应的记录 \\\\ 若采用顺序结构可以快速找到某关键字对应的记录(如折半查找)\end{cases}\end{cases} \end{cases} 顺序文件⎩ ⎨ ⎧链式存储{无论是定长/可变长记录,都无法实随机存取,每次智能从第一个记录开始依次往后查找顺序存储⎩ ⎨ ⎧可变长记录{无法实现随机存取。每次只能从第一个记录开始依次往后查找定长记录⎩ ⎨ ⎧可实现随机存取。记录长度为L,则第i个记录存放的相对位置为i∗L若采用串结构,无法快速找到某个关键字对应的记录若采用顺序结构可以快速找到某关键字对应的记录(如折半查找)
索引文件
索引表本身就是定长记录的顺序文件。因此可以快速找到第i个记录对应的索引项。
可将关键字作为索引号内容,若按照关键字顺序排列,则还可以支持按照关键字折半查找。
索引顺序文件
索引顺序文件的索引项也不需要按关键字顺序排列,这样可以极大地方便新表项的插入
索引顺序文件是索引文件和顺序文件思想的结合。索引顺序文件中,同样会为文件建立一张索引表,但不同的是:并不是每个记录对应一个索引表项,而是一组记录对应一个索引表项。
文件目录
文件控制块(FCB)
FCB的有序集合称为 “文件目录”, 一个FCB就是一个文件目录项。FCB中包含了文件的基本信息(文件名,物理地址,逻辑结构,物理结构等), 存取控制信息(是否可读/可写、禁止访问的用户名单等), 使用信息(如文件的建立时间需要、修改时间等)
单极目录结构
单极目录结构:整个文件系统只建立一张目录表。
优点:
- 实现简单
- 能实现按名存取
缺点:
- 查找速度慢
- 不允许重名
- 不便于实现文件共享
两极目录结构
两极目录结构:将文件目录分为主文件目录和用户文件目录
优点:
- 提高了目录检索速度
- 允许文件重名
- 不同用户可以使用不同的文件名来访问系统中的同一个共享文件
缺点:
- 缺乏灵活性
- 无法很好地满足文件多的用户的需要
多极目录结构
多极目录结构:两极内目录结构加以推广,允许用户文件目录在建立下级子目录,由此形成了多极目录结构,在树形目录中,主目录则称为跟目录,目录树中的非叶子节点均为目录文件(又称子目录),叶节点为数据文件。
优点:
- 层次清楚
- 允许文件重名
- 进一步提高目录检索速度
- 容易实现共享
无环图目录结构
可以用不同的文件名指向同一个文件,甚至可以指向同一个目录(共享通一目录下的所有内容)。
索引节点(FCB的改进)
当找到文件名对应的目录项时,才需要将索引结点调入内存,索引结点中记录了文件的各中心信息,包括文件的外存中的存放位置,根据 “存放位置” 即可找到文件。
文件操作
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
/*
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
- pathname:要打开的文件路径
- flags:对文件的操作权限设置还有其他的设置
O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
返回值:返回一个新的文件描述符,如果调用失败,返回-1
errno:属于Linux的系统函数库,库里面的一个全局变量,记录的是最近的错误号。
#include <stdio.h>
void perror(const char *s);
s参数: 用户描述,比如hello,最终输入的内容是 hello:xxx(实际错误的描述)
//创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
*/
int main() {
int fd = open("a.txt",O_RDONLY);
if (fd == -1) {
perror("open");
}
//关闭
close(fd);
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname:要打开的文件路径
- flags:对文件的操作权限设置还有其他的设置
-必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个之间是互斥的
-可选项:0_CREAT 文件不存在,创建新文件
-mode:八进制的数, 表示用户对创建处的新的文件的操作权限, 比如:0775
最终的权限是:mode & ~umask
0777 -> 11111111
& 0775 -> 11111101
----------------------------
11111101
按位与:0和任何数都为0
umask的作用就是抹去某些权限
flags参数是一个int类型的数据,占4个字节,32位
flags 32个位,每一位就是一个标志位。
*/
//创建一个新的文件
int fd = open("create.txt",O_RDONLY | O_CREAT, 0777);
if (fd == -1) {
perror("open");
}
close(fd);
return 0;
}
/*
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd: 文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf: 需要读取数据存放的地方, 数组的地址
- count: 指定数组的大小
返回值:
- 成功:
>0: 返回实际的读取到的字节数
=0: 文件已经读完了
- 失败: -1, 并且设置errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入数据
- count:要写得数据的实际大小
返回值:
成功:实际写入的字节数
失败:返回-1,并设置errno
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
// 1.通过open打开English.txt文件
int srcfd = open("English.txt", O_RDONLY);
if((srcfd == -1))
{
perror("open");
return -1;
}
// 2.创建一个新的文件(拷贝文件)
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
if(destfd == -1)
{
perror("open");
return -1;
}
// 3.频繁的读写操作
char buf[1024] = {0};
int len = 0;
while((len = read(srcfd, buf, sizeof(buf))) > 0)
{
printf("%d\n",len);
write(destfd,buf,len);
}
// 4.关闭文件
close(srcfd);
close(destfd);
return 0;
}
/*
标准C库的函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
Linux系统函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符,通过open得到的, 通过这个fd操作某个文件
- offset: 偏移量
- whence:
SEEK_SET:
设置指针的偏移量
SEEK_CUR:
设置偏移量,当前位置 + 第二个参数offset的值
SEEK_END:
设置偏移量,文件大小 + 第二个参数offset的值
返回值:返回文件指针的位置
作用:
1.移动文件指针到头文件
lseek(fd, 0, SEEK_SET);
2.获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
3.获取文件长度
lseek(fd, 0, SEEK_END);
4.拓展文件的长度,当前文件10b,110b,增加100字节
lseek(fd, 100, SEEK_END);
注意:需要写一次数据
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd = open("hello.txt",O_RDWR);
if(fd == -1)
{
perror("open");
return -1;
}
//拓展文件的长度
int ret = lseek(fd, 100, SEEK_END);
if(ret == -1)
{
perror("lseek");
return -1;
}
//写入一个空数据
write(fd, " ", 1);
//关闭文件
close(fd);
return 0;
}
阻塞与非阻塞
- 阻塞 (block):
- 在阻塞文件中,进程执行系统调用,如:scanf,该进程会被置为睡眠(Sleep)状态,因为资源不可达,这时内核可以调度其他进程运行,直到进程等待的事情发生才有可能继续运行。
- 缓冲 (buffer)
- 缓冲区,又称缓存,是内存的一部分。在内存中预留一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就是缓冲区。