1、实现CP命令
vimdiff file1 file2 vimdiff是Vim编辑器的一个功能,主要用于比较两个或多个文件之间的差异,并在一个Vim窗口中显示这些差异。这个功能特别适合用于比较修改前后的文件,或者比较两个不同版本的文件。
注意:用read的返回值判断要写的数据长度和停止读写的条件
cp功能实现
#include<43func.h>
int main(int argc,char *argv[]){
//cp src dest
ARGS_CHECK(argc,3);
int fdr = open(argv[1],O_RDONLY);
ERROR_CHECK(fdr,-1,"open fdr");
int fdw = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
ERROR_CHECK(fdw,-1,"open fdw");
char buf[4096] = {0};
while(1){
memset(buf,0,sizeof(buf));
ssize_t ret = read(fdr,buf,sizeof(buf));
if (ret == 0)
{
break;
}
write(fdw,buf,ret);//ret表示读多少写多少;
}
close(fdr);
close(fdw);
}
一个程序读取,一个程序写,用管道连接就可以实现远程拷贝。
性能问题(代码优化)
充分利用缓冲系统来减少指令预测。
系统调用和普通函数的区别:
上下文切换:系统调用:涉及从用户模式切换到内核模式,这需要进行上下文切换。上下文切换的过程包括保存当前用户进程的上下文环境,切换到内核模式,执行系统调用处理函数,然后恢复用户进程的上下文环境。由于上下文切换的开销,系统调用的执行通常比函数调用更加耗时。普通函数:只涉及在程序内部的代码跳转,不需要上下文切换。因此,函数调用的执行速度更快。
功能和权限:系统调用:提供了对操作系统功能和资源的访问权限。它们可以执行特权操作,例如文件系统操作、进程管理、网络通信等。由于系统调用运行在内核模式下,它们可以执行更底层的操作,但同时也受到操作系统的限制。普通函数:只能执行定义在程序中的功能代码。它们没有直接的访问操作系统底层功能的权限,只能通过系统调用间接访问。
调用方式:系统调用:通过软中断或陷阱指令触发。当应用程序需要执行特权操作时,它会使用特定的系统调用号调用对应的系统调用函数。普通函数:通过在程序中调用函数的名称来触发。函数可以是程序内部定义的,也可以是外部库提供的。
作用范围:系统调用:是用户进程与操作系统内核交互的接口,用于请求操作系统内核提供的服务和资源。普通函数:在程序内部定义和使用,用于封装和复用特定的功能代码。
安全性和稳定性:系统调用:由于涉及到操作系统内核和底层资源的访问,系统调用在设计和实现时需要特别考虑安全性和稳定性。不当的系统调用可能导致系统崩溃或数据损坏。普通函数:主要关注功能的正确性和效率,安全性和稳定性的要求相对较低。
用户态使用系统调用时,会陷入到内核态,进入内核态和进入用户态都要消耗时间。
当用户态缓冲区较大时,需要切换用户态和内核态的次数就会变少,节省了时间。
buf越大越好(减少状态切换的次数)。4096字节。
fopen,fread,fwrite, 作为c的库函数,但是要用到用户态缓冲,也会创建文件对象,在内核态中。
fopen在内核中创建了文件对象,在用户态中有一个文件流,所以该函数的buf会写入缓冲(缓冲是在用户态中的文件流实现的),
缓冲的类型:
- 全缓冲(Full Buffering):
- 定义:当填满标准I/O缓存后才进行实际的I/O操作。
- 典型代表:对磁盘文件的读写操作通常使用全缓冲。
- 刷新时机:缓冲区满时、执行flush语句、执行endl语句或关闭文件时,会刷新缓冲区进行真正的I/O操作。
- 行缓冲(Line Buffering):
- 定义:在输入和输出中遇到换行符时,执行真正的I/O操作。
- 典型代表:键盘输入数据通常采用行缓冲,即输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。
- 刷新时机:与全缓冲类似,但主要区别在于刷新时机是遇到换行符时。
- 不带缓冲(No Buffering):
- 定义:不进行缓冲,数据直接进行I/O操作。
- 典型代表:标准出错情况stderr是典型的不带缓冲输出,这使得出错信息可以直接尽快地显示出来。
- 刷新时机:由于不进行缓冲,所以没有特定的刷新时机。
当buf给file缓冲区传输数据时,一般等缓冲区满了才会传输数据到用户态的文件流。
使用用户态的fopen,fread,fwrite,
优势:(零碎写入读写文件使用文件流时,可以最大程度上保证用户态和内核态切换次数处于比较少的情况。
通过缓冲区,可以将多次小型的读写操作合并成一次较大的操作,从而提高了磁盘I/O的效率)
劣势:(拷贝次数更多。当数据从磁盘读入到用户态的缓冲区时,或者从用户态的缓冲区写入到磁盘时,都会发生内存拷贝。这些拷贝操作会增加CPU的开销。
在用户态和内核态之间维护缓存可能会导致缓存一致性问题。如果内核态的缓冲区或磁盘上的数据被其他进程或系统调用修改,而用户态的缓冲区没有及时更新,就可能导致数据不一致。
文件流缓冲区的管理(如分配、释放、刷新等)需要额外的代码和逻辑来处理,这增加了编程的复杂性。)
文件截断:ftruncate(按字节截断文件)可以用于创建一个固定大小的文件。
#include <43func.h>
int main(int argc ,char *argv[]){
ARGS_CHECK(argc,2);
int fd = open(argv[1],O_RDWR);
ERROR_CHECK(fd,-1,"open");
int ret = ftruncate(fd,20);
ERROR_CHECK(ret,-1,"ftruncate");
}
大文件截断成小文件就直接去掉,小文件截断成大文件直接补0.二进制0.。截断是直接影响文件大小。
stat命令在Linux系统中用于显示文件或文件系统的状态信息
用该系统调用截断成40960大小。
8个BLOCK,一个block假设为512byte,实际上给了4096空间,但具体大小只有414byte。所以形成文件空洞。
文件总大小为40960,4096分配了磁盘。
操作系统不会给空出来的区域分配磁盘,可以把空出来的区域叫做文件空洞。
文件空洞的概念
定义:
在UNIX文件系统中,文件位移量(offset)可以大于文件的当前长度。当对这样的文件进行写操作时,文件会被“撑大”,并在文件中构成一个空洞(hole)。空洞是文件中没有实际写入数据的部分,由重复的0表示。
特点:
- 不占用磁盘空间:在写入数据之前,空洞并不占据磁盘空间。文件系统会将其解释为0的子串,并将对应链表的指针设为空。
- 预留磁盘空间:虽然空洞本身不占用磁盘空间,但文件系统会扣减程序可用的磁盘空间数值大小,以实现预留。
- 读写行为:使用
read
函数读取空洞部分时,返回的数据是0。使用cp
命令拷贝的文件,空洞部分不会被拷贝,因此生成的同样文件占用磁盘空间较小。
使用场景
1. 下载数据:
- 迅雷下载:在下载过程中,迅雷会先创建一个与最终文件大小相同的空洞文件,以便在多线程下载时从不同的地址写入数据。这样可以确保即使磁盘空间被其他程序占用,下载也不会因磁盘空间不足而中断。
- 预留磁盘空间:在下载大文件时,利用文件空洞可以确保磁盘空间被预留,从而避免下载过程中因磁盘空间不足而中断。
2. 数据库文件:
- 压缩和优化:数据库文件通常包含大量空洞。通过使用文件空洞填充技术,可以压缩数据库文件的大小,提高存储效率和读写性能。
3. 网络开发:
- 优化传输:在网络传输中,文件空洞填充技术可以用于压缩网络数据包的大小,减少数据传输的时间和带宽占用。例如,在文件传输协议中,可以将逻辑上的空洞视为本地主机上的零字节,以减少传输数据量。
4. 虚拟机存储:
- 优化存储:在虚拟机环境中,文件空洞填充可以用于优化虚拟机磁盘的存储空间,提高存储效率。