LinuxGod.net
Linux大神网——精选每一篇高品质的技术干货
  1. 首页
  2. 开源快讯
  3. 正文

本篇通过LinuxOS文件操作系统函数实现copy底层实现命令

2023年3月18日 273点热度

继《Java文件的简单读写、随机读写、NIO读写与使用MappedByteBuffer读写》,本篇通过调用LinuxOS文件操作系统函数实现copy命令以加深我们对Java文件读写底层实现的理解。

本篇内容包括:

文件操作系统函数

本篇将介绍的函数有:open、close、write、lseek、read、mmap、msync。使用Linux下系统调用函数来进行对文件的操作须要导出头文件、,安装gcc后这两个头文件位置在/usr/include/目录下。mmap例外,使用mmap须要导出头文件,该文件位置在/usr/include/sys目录下。

open函数

open函数用于打开一个文件获取文件句柄,当文件不存在时支持创建文件再打开文件。

int open(const char* filename,int flag,int mode);

该方式执行成功会返回文件描述符(文件句柄),失败则返回-1,参数说明如下:

flag参数的可取值如下:

(以上这三个参数只能选择其中一个arch linux,但可以与下述多个参数通过or运算组合使用)

上一篇文章笔者有提及,就是我们创建FileOutputStream时,假若不指定append为true,这么会造成文件内容被清空,缘由是FileOutputStream构造函数调用了open函数,open函数判定append函数倘若为false,则flags会组合O_TRUNC(假如文件存在,则文件将被截断,即厚度为0),因而会造成文件被清空linux格式化硬盘,源码如下。

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_open0(JNIEnv *env, jobject this,
                                    jstring path, jboolean append) {
    fileOpen(env, this, path, fos_fd,
             O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
}

model可取的值如下,支持或(or)运算:

close函数

close函数用于关掉已打开的文件。

int close(int fd);

方式执行成功返回0,失败返回-1,参数说明如下:

read函数

read函数用于读取文件数据到显存中。

int read(int fd,void * buff,int count);

方式执行成功返回本次真正读取到的字节数,读取到文件末尾返回0,出现异常返回-1,参数说明如下:

write函数

write函数用于向文件中写入数据。

int write(int fd,void* buff,int count);

方式执行成功返回本次真正写入文件的字节数,出现异步返回-1,参数说明如下:

lseek函数

lseek函数用于将文件读写表针联通到文件的指定偏斜量位置处。

int lseek(int fd,int offset,int seek_flag);

方式执行成功返回文件新的偏斜量,出现异常返回-1,参数说明如下:

seek_flag可取值如下:

mmap函数

void * mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

该方式执行成功返回可用的显存首地址,失败则返回-1,各参数说明如下:

prot可取值如下,可以通过or运算组合在一起:

Java只支持使用PROT_READ或则PROT_READ与PROT_WRITE组合。

flags可取值也好多,可以通过or运算符组合在一起,但因为Java只使用了MAP_SHARED与MAP_PRIVATE,因而这儿只介绍这两种取值:

Java的FileChannel的mmap方式实际调用的navite方式的源码如下:

JNIEXPORT jlong JNICALL
Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this,
                                     jint prot, jlong off, jlong len)
{
    void *mapAddress = 0;
    jobject fdo = (*env)->GetObjectField(env, this, chan_fd);
    // 获取文件句柄
    jint fd = fdval(env, fdo);
    int protections = 0;
    int flags = 0;
    // 解析port和flags取值
    if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) {
        protections = PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) {
        protections = PROT_WRITE | PROT_READ;
        flags = MAP_SHARED;
    } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) {
        protections =  PROT_WRITE | PROT_READ;
        flags = MAP_PRIVATE;
    }
    // 调用mmap方法
    mapAddress = mmap64(
        0,                    /* Let OS decide location */
        len,                  /* Number of bytes to map */
        protections,          /* File permissions */
        flags,                /* Changes are shared */
        fd,                   /* File descriptor of mapped file */
        off);                 /* Offset into file */
    // 映射失败
    if (mapAddress == MAP_FAILED) {
        ...... 
    }
    return ((jlong) (unsigned long) mapAddress);
}

从Java_sun_nio_ch_FileChannelImpl_map0方式源码可以看出,Java仅支持使用的flags为MAP_SHARED或MAP_PRIVATE,支持使用的prot为PROT_READ或则PROT_WRITE"+"PROT_READ。

msync函数

倘若采用显存映射文件的形式读写文件linux mmap 文件,须要调用msync确保更改的内容完全同步到硬碟之上。

int msync(void *addr, size_t length, int flags)

msync须要指定同步的地址区间,这么细细度的控制虽然比fsync愈发高效,由于应用程序一般晓得自己的脏页位置。(但实际上Linuxkernel才能很快地找出文件的脏页,致使fsync只会同步文件的更改内容。)

Java的MappedByteBuffer提供的force方式底层就是调用msync方式,如下源码所示。

JNIEXPORT void JNICALL
Java_java_nio_MappedByteBuffer_force0(JNIEnv *env, jobject obj, jobject fdo,
                                      jlong address, jlong len)
{
    void* a = (void *)jlong_to_ptr(address);
    int result = msync(a, (size_t)len, MS_SYNC);
    if (result == -1) {
        JNU_ThrowIOExceptionWithLastError(env, "msync failed");
    }
}

实战:实现文件拷贝命令

编撰一个c程序实现对文件的拷贝,指定源文件拷贝输出到指定目标文件,并将程序所在目录加入到系统环境变量中linux mmap 文件,实现自定义文件拷贝命令。

首先是实现打开文件与关掉文件,代码如下。

#include 
#include 
// 定义每次拷贝的字节长度
#define ONE_LINE_SIZE 100
void closeFile(int fd);
// 以只读方式打开一个文件
// 第一个参数:包含文件路径的文件名,这里应该传源文件名,即被拷贝的文件名
// return返回文件标识符
int openReadFile(char* fileName){
    int fd open(fileName, O_RDONLY, S_IRWXU);
    if(fd==-1){
        printf(" open read file error !n");
    }else{
        printf("open read file success! n");
    }
    return fd;
}
// 以只写方式打开一个文件,文件不存在则创建,如果文件存在则写入的内容将会覆盖原有的
// 第一个参数:包含文件路径的文件名,这里应该传自标文件茗
// return返回文件标识符
int openWriteFile(char* fileName){
    int fd= open(fileName, o WRONLY IO CREAT, S IRWXU):
    if(fd!=-1){
        printf ("open write file success! n")
    }else{
        printf ("open write file error!n");
    }
    return fd;
}
//关闭文件
//第一个参数:文件描述符
void closeFile(int fd){
    if(fd==-1){
      return;
    }
   if(close(fd)==0){
      printf("file close success! n");
   }else{
      printf( "file close fail! n");
   }
}

接着实现文件的拷贝逻辑,从原文件读取数据写入目标文件,代码如下。

// 声明拷贝方法
void copy(char* sFILeName, char* dFileName);
// 声明执行读写的方法
void copyFile(int sfd, int dfd);
// 第一个参数:源文件名
// 第二个参数:目标文件名
void copy (char* sFileName, char* dFileName){
    int sfd openReadFile(sfileName);
    if(sfd==-1){
      printf("copy fail!n");
      return;
    }
    int dfd openWriteFile(dFileName);
    if(dfd==-1){
       printf("copy fail!n");
       closeFile(sfd);
       return;
    }
    copyFile(sfd, dfd);
    closeFile(sfd);
    closeFile(dfd);
}

copy方式主要实现文件的打开和关掉,在文件打开出错的情况下不执行真正的文件拷贝操作,倘若指定的两个文件都打开成功,这么将返回的文件描述符传递给copyFile方式实现真正的拷贝动作。

真正实现拷贝逻辑的copyFile方式代码如下。

// 真正执行拷贝的方法
// 第一个参数:源文件的文件句柄
// 第二个参数:目标文件的文件句柄
void copyFile(int sfd, int dfd) {
    char buff[ONE_LINE_SIZE+1];
    int sLseekOffset = 0;
    int dLseekOffset = 0;
    int rLength;
    int countSize=0;
    char flag = 1;
    // 出头开始
    (void)lseek(sfd, O, SEEK_SEK);
    (void)lseek(dfd, O, SEEK_SEK);
    // 循环读文件
    while(-1 != (rLength = read(sfd, buff, ONE_LINE_ SIZE))){
        if(rLength==0){
            flag=0;
            break;
        }
        countSize += rLength;
        // 写到另一个文件
        int writeSize = write(dfd, buff, rLength);
        if(writeSize != rLength){
            flag=0;
            printf( "copy fail! read or write error !n");
            break;
        }
        dLseekOffset = lseek(dfd, 0, SEEK_CUR);
        sLseekOffset = lseek(sfd, 0, SEEK_CUR);
        if(sLseekOffset != dLseekOffset) {
            flag=0;
            printf("copy fail! seek error!n");
            break;
        }
        printf("current copy byte size %d, s_seek is %d, d_seek is %d, count copy byte size is %dn, rLength, sLseekOffset, dLseekOffset, countSize);
    }
    if(flag == 1) {
        printf( "file copy success!n");
    }
}

其中rLength用于保存每次调用read方式实际读取的字节数,假若它的值为0,说明读取到了文件末尾,假如值为-1说明读取出错,不管是读取到文件尾还是读取出错都应当停止循环。

lseek(dfd,0,SEEK_CUR)和lseek(sdf,0,SEEK_CUR)并没有改变文件的偏斜量,只是拿来获取当期文件相对文件开始位置的偏斜量,由于read和write执行完后就会将文件偏斜量往前联通读取或写入的字节数,所以不须要自动调用lseek改变文件偏斜量。

buff用于保存读取的文件的内容,之后再将读取的内容写入到新文件中,循环该操作直至读取到的字节数为0就正常完成了拷贝。

程序入口方式的实现代码如下。

// 关于参数说明:
// 例如:filecopy test1.txt test2.txt
// argc=3
// argv[0]:filecopy命令的字符串
// argv[1]:源文件名
// argv[2]:目标文件名
int main(int argc, char* argv[]){
    switch(argc)[
        case 3:
           copy (argv[1], argv[2]);
           break;
        default:
           printf("input parameters error !n");
           break;
    }
    return 0;
}

main方式主要做的事情是判定参数的完整性,参数不完整程序执行失败,当参数完整时调用copy方式进一步判定输入的参数是否有效。假如参数完整这么传入main方式的参数argc等于3,argv的宽度为3,第一个参数为可执行文件的文件名,第二个参数必须是指定要拷贝的文件的文件名(包含路径信息,可以是绝对路径也可以是相对路径),第三个参数是新的文件名即保存拷贝内容的文件的文件名,要求同第二个参数。

程序编撰完成后,首先使用gcc将源程序编译成可执行文件:

gcc filecopy.c -o ./filecopy

接着使用export命令为系统添加一个临时的环境变量:

# 由于笔者将编译后的可执行文件filecopy保存在“/home/wujiuye/桌面/c_projects/linux/file”下
export PATH:$PATH:/home/wujiuye/桌面/c_projects/linux/file

如今我们就可以像使用系统命令一样来使用自己写的文件拷贝程序了。

将终端关掉,重新打开后步入tmp目录,随意找一个文件测试,这儿我找了一个图片文件进行测试,输入:

filecopy qt-trayicon-ns2416.png news.png

运行过程及结果如右图,其中qt-trayicon-ns2416.png是要拷贝的文件名,news.png为通过拷贝生成的新文件的文件名。

此时使用文件管理器步入到tmp目录下可以看见拷贝成功后的news.png文件,如右图。

实战:使用mmap实现文件拷贝命令

拷贝命令须要实现给定一个原文件,创建一个目标文件,通过mmap将原文件数据拷贝到目标文件。程序完整代码如下。

#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[]) {
    if (argc != 3) { return -1; }
    // 打开文件获取文件句柄
    int sfd, dfd;
    if ((sfd = open(argv[1], O_RDONLY, S_IRWXU)) < 0) { return -1; }
    if ((dfd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRWXU | S_IRWXG | S_IRWXO)) < 0) { return -1; }
    // 获取原文件的大小
    int size = lseek(sfd, 0, SEEK_END);
    lseek(sfd, 0, SEEK_SET);
    // 设置文件大小
    truncate(argv[2], size);
    // mmap映射
    void *src, *dst;
    if ((src = mmap(0, size, PROT_READ, MAP_SHARED, sfd, 0)) == MAP_FAILED) { return -1; }
    if ((dst = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, dfd, 0)) == MAP_FAILED) { return -1; }
    // 关闭文件
    close(sfd);
    close(dfd);
    // 直接内存拷贝
    memcpy(dst, src, size);
    // 执行同步操作
    msync(dst, size, MS_SYNC);
    return 0;
}

此例中用到的其它c方式说明:

该程序第一步是使用open系统调用打开文件获取文件句柄;第二步是通过lseek估算获取文件大小(这是笔者参照Java的RandomAccessFile类的length方式获取文件大小的实现);第三步是改变目标文件的大小,让目标文件的大小与原文件大小一样;第四步是关掉文件,映射成功后就早已不须要用到文件句柄了;第五步是直接显存拷贝,将原文件映射的显存拷贝到目标文件映射的显存;最后调用msync同步显存修改到文件中。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: mmap 拷贝
最后更新:2023年3月18日

Linux大神网

每日更新,欢迎收藏♥ 不积跬步无以至千里,加油,共勉。

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

Linux大神网

每日更新,欢迎收藏♥
不积跬步无以至千里,加油,共勉。

最新 热点 随机
最新 热点 随机
VMware过期Linux系统CentOS7下载安装1.77下载地址 红旗linux操作系统v11.0-红旗操作系统操作系统吗? 5yw红软基地驱动完美支持windows、mac、linux系统 轻松安装ZeroMQ:Ubuntu系统必备的解决方案 怎么在Win7系统虚拟机上安装LinuxUbuntu的技巧?在这里 Linux必备!安装RAR解压器,轻松解压文件! 虚拟机linux系统下载 什么是linuxLinux(/托瓦兹)的Linux内核 文件永久删除还能找回来吗?关于Win和Mac系统的恢复方法 Ubuntu16.04怎样安装系统更新和应用更新16.04 网卡 centos 启动 Linux选择选择时各企业所参考的依据:以下内容和Centos Linux内核内存检测工具系列中的一篇,配置以及典型应用 虚拟机安装Ubuntu操作系统-Ubuntu空间20G镜像下载 0渗透操作0x02绕过disable_functions插件(组图) 【好玩的网络-第2.5期】分配IP有哪些骚操作?小白保姆级教程 Windows远程连接工具、Ubuntu系统的安装软件及系统 Linux一模一样远程连接编辑的操作图是什么? DRAM中的虚拟地址空间的缓存简化内存管理(组图) 2.5创建和配置虚拟机2.5.1创建虚拟机双击桌面的VMwarePlayer 掌握Linux基础命令-上海怡健医学系统 服务器上安装linux系统的具体操作过程-如何选择系统?
Linux文件路径查询方法,轻松获取文件位置Linux移植6410:从零开始的挑战!linux php安装 快速掌握Linux下PHP安装,轻松入门流行技术linux patch 文件 解决LinuxPatch文件10大疑难杂症,轻松应对!Ubuntu系统更改IP地址的简易教程,跟随以下步骤即可搞定!高效搭建Linux Android开发环境,轻松提升开发体验轻松掌握:如何查询Linux内核版本?Linux安装Matlab指南,快速掌握安装步骤Ubuntu 16安装网卡驱动教程,轻松解决网络连接问题Linux启动时,不使用图形界面可能更有效率!移植Linux,手机变身“大杀器”!详解8大关键操作Linux更高效,掌握启动终端快捷键!纯Python库实现上面介绍的HTML转换为PDF的软件Fedora 13服务器配置指南:9个实用技巧全解析9种方法轻松获取Linux版本,快速掌握系统信息!博客韦东山freeRTOS系列教程:入门文档教程+进阶视频教程轻松掌握linux ldd命令的技巧,成为高效开发者与管理员Linux VPS安全检测,保障服务器稳定运行!Linux下高效开发必备:配置Tomcat和JDK分析Linux中的I2C驱动程序框架核心结构(i2c-bus结构)
2021-12-31sudo,chmod命令的使用应用场景 Ubuntu14.04安装JDK1.8.0_25与配置环境变量过程笔记 VR物联网智能家居实训套件你能get的技能精致并不是 更为丰富多彩快手直播伴侣app是一款爱看什么视频就能将视頻 揅笑晏当硬盘分区完成后,需要对分区进行格式化 9种方法轻松获取Linux版本,快速掌握系统信息! 虚拟机安装Ubuntu操作系统-Ubuntu空间20G镜像下载 centos系统版本详细信息解决方案与操作系统版本兼容的重要性分析 嵌入式Linux软件设计系统设计的一般流程是什么?(图) 我试图将从java作业获得的时间信息与linux性能监视工具 --version安装git版本太老-yinstallgit 如何在Linux上运行.exe文件的唯一方法?-八维教育 TCL电视看电视直播方法购买智能电视的热卖,不少 如何在Linux上安装虚拟机的结果大多都是怎么安装的 -max=999999net.2 一下美国Linux服务器的系统优化优化方法方法 Linux下的安装zip解压功能及安装unzip教程 mkfsext3devhdbfdisk命令查看当前Linux分区情况可输入这条Linux命令 虚拟机linux系统下载 什么是linuxLinux(/托瓦兹)的Linux内核 华为手机刷机软件V1.2.5官方版软件说明软件介绍
标签聚合
文件目录 linux系统 linux脚本 linux服务器 虚拟机 软件 命令模式 sudo 命令 电脑
书籍
课程
技术群
技术干货大合集↓
  • 2023年3月 / 112篇
  • 2023年2月 / 84篇
  • 2023年1月 / 161篇
  • 2022年12月 / 187篇
  • 2022年11月 / 76篇
友情链接:

Linux书籍 | Linux命令 | Linux系统 | RHCE红帽认证 | Linux软件 | Linux教程 | CentOS系统 | Linux内核 | Linux服务器 | Linux大神 | IT资源

COPYRIGHT © 2023 linuxgod.net ALL RIGHTS RESERVED.