linux学习之旅(22)----内存空间映射(mmap)

mmap()函数用来将文件或者设备空间映射到内存中,这样对文件的读写就可以直接通过指针来操作,而不需要read/write函数。这样就会加快文件操作的速度。

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

函数功能:

将文件或者设备空间映射到内存中。

参数说明:

(1)addr:文件映射到内存的地址,通常为NULL,表示由系统决定映射到什么地址。

(2)length:表示映射数据的长度,即文件需要映射到内存中的大小。

(3)prot:表示映射区保护。

PROT_EXEC:表示映射区域可执行

PROT_READ:表示映射区域可读取

PROT_WRITE:表示映射区域可写入

PROT_NONE:表示映射区域不能存取

(4)flags:用于设定映射对象的类型、选项和是否可以对映射对象进行操作。

MAP_FIXED:如果参数addr指定了需要映射的地址,而所指的地址无法成功的建立映射,则映射失败。所以通常将addr的值设置为NULL

MAP_SHARED:共享的映射区域,即映射区域允许其他进程共享,对映射区域的写入的数据将会写入到原来的文件中。

MAP_PRIVATE:当对映射区域进行写入操作时会产生一个映射文件的复制,即写入复制(copy on write),而读操作不会影响此复制。对此映射区的修改不会写回原来的文件,即不会影响原文件的内容。

MAP_ANONYMOUS:建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射的区域无法和其他进程共享

MAP_DENYWRITE:对文件的写入操作将被禁止,只能通过对此映射区操作的方式实现对文件的操作,不允许直接对文件操作

MAP_LOCKED:将映射区锁定,此区域不会被虚拟内存重置。

(5)fd:文件描述符。

(6)offset:从文件的什么位置开始映射(必须是页大小的整数倍(4k))。

返回值:映射区内存的首地址

int munmap(void* addr,size_t length);

关闭映射区空间。

程序1:利用mmap函数修改文件内容

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int fileFlags=open("./test",O_RDWR);
    if(fileFlags<0)
    {
        perror("open");
        exit(1);
    }
    char str[]="Today is really good";
    int length=strlen(str);
    //利用lseek函数拓展文件
    lseek(fileFlags,length,SEEK_SET);
    //必须进行一次写入
    write(fileFlags,'a',1);
    //mmap(void* addr,size_t length,int prot,int flags,int fileFlag,off_t offset);
    char *p=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fileFlags,0);
    if(p==MAP_FAILED)
    {
        perror("mmap");
        exit(1);
    }
    //将文件映射到内存中后,原文件就可以删除了。
    close(fileFlags);
    strcpy(p,str);
    munmap(p,length);
    return 0;
}

利用mmap函数实现多进程拷贝文件

思路:利用进程和mmap函数将文件分块映射到内存中然后分块拷贝,每个线程负责拷贝的自己的部分。因为off必须为4096的整数倍,所以将文件按4096的大小分份(最后一份的大小小于4096).

这个介绍一个函数:

void* memcpy(void* tarStr,const void* sourStr,size_t len);

 函数功能:从源内存地址起始位置拷贝若干和字节到目标内存位置中。

相比于strcpy函数:

(1)strcpy函数只能拷贝字符类型,而memcpy函数可以任意类型。

(2)memcpy函数比strcpy函数灵活,可以指定拷贝的长度。

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define SIZE 4096

//线程拷贝 off必须是4096的整数倍
void processCopy(int sour,int tar,int i,int length)
{
    void* msour=mmap(NULL,length,PROT_READ,MAP_SHARED,sour,i*SIZE);
    void* mtar=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,tar,i*SIZE);
    if(msour==MAP_FAILED||mtar==MAP_FAILED)
    {
        perror("mmap");
        exit(1);
    }
    memcpy(mtar,msour,length);
    munmap(msour,length);
    munmap(mtar,length);
    printf("我是进程:%d 我的拷贝任务已完成。\n",getpid());
}

int main(int argc,char* argv[])
{
    if(argc<3)
    {
        printf("./mycp_p sourFile tarFile [num]\n");
        exit(1);
    }
    int sourFile=open(argv[1],O_RDONLY);
    //原文件不存在
    if(sourFile<0)
    {
        perror("open");
        exit(1);
    }
    int tarFile=open(argv[2],O_RDWR|O_CREAT|O_EXCL,0744);
    //目标文件已经存在
    if(tarFile<0)
    {
        perror("open");
        exit(1);
    }
    //计算文件大小
    int fileSize=lseek(sourFile,0,SEEK_END);
    //计算线程数
    int proNum;
    proNum=fileSize/SIZE;
    if(fileSize%SIZE!=0)
    {
        ++proNum;
    }
    lseek(tarFile,fileSize-1,SEEK_SET);
    write(tarFile,"a",1);
    int i=0;
    int pid;
    for(;i<proNum;++i)
    {
        pid=fork();
        if(pid==0)
        {
            break;
        }
    }
    int proSize;
    if(pid==0)
    {
        //剩余的部分需要最后一个进程多拷贝一些
        if(i==proNum-1)
        {
            proSize=fileSize%SIZE;
        }
        else
        {
            proSize=SIZE;
        }
        processCopy(sourFile,tarFile,i,proSize);
        sleep(1);
        
    }
    else if(pid>0)
    {
        //等待回收子进程
        wait(NULL);
    }
    else
    {
        perror("fork");
        exit(1);
    }
    close(sourFile);
    close(tarFile);
    return 0;
}

这个程序的特点是文件越大开的进程数越多,所以理论上无论文件有多大拷贝需要的时间都是相同的(仅仅只是理论),也不用担心文件过大内存放不下的问题。但一旦文件过大就会导致开的进程数超出系统的限制,就会导致文件失败(可以通过修改SIZE参数解决)。

程序3:利用mmap函数实现多线程拷贝

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>

//定义最大线程数(可修改)
#define MAXPTHNUM 1024
//线程会共享全区变量
void *sourAddr,*tarAddr;

struct PthreadData
{
    int num;//线程编号
    int pOff;//指针偏移量
    int len;//需要拷贝数据大小
};
typedef struct PthreadData PTD;

//将参数封装到结构体中
void* pthreadCopy(void* pthData)
{
    PTD *ptd=(PTD*)pthData;
    int off=ptd->num*ptd->pOff;
    //mencpy(目标文件,源文件,拷贝长度);
    memcpy(tarAddr+off,sourAddr+off,ptd->len);
    printf("我是线程%x:任务已完成。(线程编号:%d,拷贝文件大小:%d)\n",(unsigned int)pthread_self(),ptd->num,ptd->len);
    pthread_exit((void*)1);
}

//文件预处理:判断输入是否合法--》打开并拓展文件
int filePrepro(int fileNum,char *fileName[],int *sour,int *tar,int *pNum)
{
    if(fileNum<3||fileNum>4)
    {
        printf("mycp_t sourFile tarFile [pthread number]\n");   
        return 0;
    }
    if(fileNum==4)
    {
        //用户输入的线程数
        *pNum=atoi(fileName[3]);
    }
    *sour=open(fileName[1],O_RDONLY);
    if(*sour<0)
    {
        perror("Open Sour File");
        return 0;
    }
    *tar=open(fileName[2],O_RDWR|O_CREAT|O_EXCL,0744);
    if(*tar<0)
    {
        perror("Open Tar File");
        return 0;
    }
    int fileSize=lseek(*sour,0,SEEK_END);
    lseek(*tar,fileSize-1,SEEK_SET);
    write(*tar,"a",1);
    if(fileSize!=(lseek(*tar,0,SEEK_END)))
    {
        close(*sour);
        close(*tar);
        printf("文件拓展失败!\n");
        return 0;
    }
    return fileSize;
}

int main(int argc,char *argv[])
{
    //默认线程数为5
    int i=0;
    int sourFile,tarFile,pthNum=5;
    int fileSize=filePrepro(argc,argv,&sourFile,&tarFile,&pthNum);
    if(fileSize==0)
    {
        exit(1);
    }
    //将源文件映射到内存中
    sourAddr=mmap(NULL,fileSize,PROT_READ,MAP_SHARED,sourFile,0);
    //将目标文件映射到内存中
    tarAddr=mmap(NULL,fileSize,PROT_READ|PROT_WRITE,MAP_SHARED,tarFile,0);
    //关闭文件
    close(sourFile);
    close(tarFile);
    int j=0;
    //计算每个线程需要拷贝的部分
    int pthSize=fileSize/pthNum;
    int proSize=fileSize%pthNum;
    pthread_t tid[MAXPTHNUM];
    PTD ptd[MAXPTHNUM];
    for(i=0;i<pthNum;++i)
    {
        ptd[i].pOff=pthSize;
        ptd[i].len=pthSize;
        ptd[i].num=i;
        if(i==pthNum-1)
        {
            ptd[i].len=ptd[i].len+proSize;
        }
        pthread_create(&tid[i],NULL,pthreadCopy,(void*)(&ptd[i]));
    }
    for(j=0;j<pthNum;++j)
    {
        //主控线程等待其他线程终止(阻塞)
        pthread_join(tid[j],NULL);
    }
    //解除映射
    munmap(sourAddr,fileSize);
    munmap(tarAddr,fileSize);
    return 0;
}

 

(如果文件过大就需要分块映射,这个程序没有实现)

多进程和多线程的对比:

1、多线程中支持用户自定义线程个数,而多进程不支持。

2、多线程中程序只需要mmap一次,而多进程需要多次。

原因:在多进程下信息通讯不方便,再加上mmap的特点(偏移量必须为4096的整数倍,即页的整数倍),所以指定进程数就会很不方便(线程共享信息方便)。

3、多线程中每个线程多需要维护自己的变量(PTD),而进程中需要一个。

原因:多线程中线程会共享变量,主控线程修改变量后,其他线程中变量也会修改,所以每个线程都需要自己的变量,而进程之间相互不影响,所以只需要一个变量(进程稳定)。

猜你喜欢

转载自blog.csdn.net/qq_39038983/article/details/88690246