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