I/O可以分为 高级I/O 和 低级I/O,高级I/O 通常也称为 带缓冲的I/O,比如 ANSI C库提供的标准I/O库。低级I/O通常也称为不带缓冲的I/O,它是Linux提供的系统调用,如:open、read、write等。带缓冲的I/O在系统调用前采用一定的策略,速度慢,但比不带缓冲的I/O安全,如:fopen、fread、fwrite等。
Linux下对文件进行输入输出操作(I/O操作)有3种编程方式:
- 调用C库中文件的IO函数,如 fopen、fread/fwrite、fclose等。
- 使用Linux的系统调用。
- 使用C++的文件流操作。
一 文件描述符
对于Linux而言,所有对设备或文件的操作都是通过文件描述符进行的。当打开或者创建一个文件的时候,内核向进程返回一个文件描述符(非负整数)。后续对文件的操作只需要通过该文件描述符,内核记录有关这个打开文件的信息。一个进程启动时,默认打开3个文件,标准输入,标准输出,标准错误,对应的文件描述符是 0、1、2。文件描述符的数量也有限制,比如在centos 7.2中,文件描述使用到 100,000 的时候,就无法继续打开文件了。系统中的文件描述符已经耗尽。
文件描述符能与C语言中的文件指针相互转换:
// 将文件指针转换成文件描述符
int fileno(FILE *stream);
// 将文件描述符转换成文件指针
FILE fdopen(int fd, const char* mode); // mode是打开方式
举例:
#include <stdlib.h>
#include <stdio.h>
int main()
{
printf("fileno(stdin) == %d\n", fileno(stdin));
printf("fileno(stdout) == %d\n", fileno(stdout));
printf("fileno(stderr) == %d\n", fileno(stderr));
return 0;
}
二 打开文件
Linux提供open函数打开或者创建一个文件:
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
// flag是文件打开的方式,可以使用以下宏(当有多个选项时,采用 ‘|’ 连接)
/*
这三个选项必选其一:
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
其它是可选的:
O_APPEND 每次写操作都写入文件的末尾
O_CREAT 如果指定文件不存在,则创建这个文件
O_EXCL 如果已经置O_CREAT且文件存在,就强制open失败
O_TRUNC 打开文件时,则清空文件全部内容
同步(SYNC)选项都会降低性能,也是可选选项。
O_DSYNC 每次写入时,等待数据写到磁盘上
O_RSYNC 每次读取时,等相同的部分先写到磁盘上
O_SYNC 以同步的方式写入文件,强制刷新内核缓冲区到输入文件
*/
// mode只有创建文件时才使用,用于指定文件的访问权限
/*
S_IRUSR 所有者拥有读权限
S_IWUSR 所有者拥有写权限
S_IXUSR 所有者拥有执行权限
S_IRWXU S_IRUSR | S_IWUSR | S_IXUSR
S_IRGRP 群组拥有读权限
S_IWGRP 群组拥有写权限
S_IXGRP 群组拥有执行权限
S_IRWXG S_IRGRP | S_IWGRP | S_IXGRP
S_IROTH 其他用户拥有读权限
S_IWOTH 其他用户拥有写权限
S_IXOTH 其他用户拥有执行权限
S_IRWXO S_IROTH | S_IWOTH | S_IXOTH
*/
举例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int fd = open("./a.txt", O_WRONLY | O_CREAT, 0644);
if (fd == -1)
printf("fail to create a new file\n");
else
printf("create file success\n");
close(fd);
return 0;
}
打开新建文件时,也可以将文件权限用二进制表示 ,如上,a.txt 的权限就是 0644。
三 创建文件
creat函数声明如下:
int creat(const char* pathname, mode_t mode);
// 执行成功就返回文件描述符,否则返回-1
举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd = -1;
char filename[] = "/root/code/output.txt";
fd = creat(filename, 0666); // 文件权限为0644,会送去umask的值
if (-1 == fd)
printf("fail to open file %s\n", filename);
else
printf("create file %s successfully\n", filename);
close(fd);
return 0;
}
四 关闭文件
close函数声明如下:
#include <unistd.h>
int close(int fd);
// 执行成功就返回文件描述符,否则返回-1
文件关闭之后 ,此文件描述符不再指向任何文件,从而描述符可以再次使用。如果每次打开文件后不再关闭,就会将系统的文件描述符耗尽,导致不能打开文件。
五 读取文件
read函数声明如下:
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count)
/*
ssize_t 是描述读取字节数多少的一个内置类型,可以为负数
该函数会把参数fd所指的文件传送count字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。
返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或没有可读取的数据。
注意:文件读写位置会随读取到的字节移动
需要强调的是,如果函数读取成功,会返回实际读取到的数据的字节,最好能将返回值与参数count做比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾或者read()被信号中断了读取动作。当有错误发生时,则返回-1,错误代码存入errno中,此时文件读写位置无法预期。
关于errno:
Linux中系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。可以通过pritnf("%s",strerror(errno)); 打印错误信息。
常见的错误代码如下:
EINTR: 此调用被信号中断
EAGAIN: 当使用不可阻断I/O时(O_NONBLOCK),若无数据可读取,则返回此值。
EBADF: 参数fd为非有效的文件描述符,或当前文件已关闭。
*/
举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = -1;
ssize_t size = -1;
char buf[10];
char filename[] = "./output.txt";
fd = open(filename, O_RDONLY);
if (-1 == fd)
{
printf("Open file %s failuer, fd:%d\n", filename, fd);
return -1;
}
else
printf("Open file %s success, fd:%d\n", filename, fd);
// 循环读取数据,直到文件末尾或者出错
while(size)
{
// 读取文件中的数据,10的意思是希望讲到10个字节,但真正读到的字节数是函数的返回值
size = read(fd, buf, 10);
if (-1 == size)
{
close(fd);
printf("Read file %s error occurs\n", filename);
return -1;
}
else
{
if (size > 0)
{
printf("read %d bytes:", size);
printf("\"");
for (int i = 0; i < size; i++)
printf("%c", *(buf+i)); // 这里会把文件中的换行符也当成一个字符打印
printf("\"\n");
}
else
{
printf("reach the end of file\n");
}
}
}
close(fd);
return 0;
}
六 向文件写入数据
write函数声明如下:
#include <unistd.h>
ssize_t write(int fd, void* buf, size_t count)
/*
该函数会把参数buf所指的缓冲区中的count个字节数据写入fd所指的文件内。当然,文件读写位置也会随之移动。其中,参数fd是一个已经打开的文件描述符;buf指向一个缓冲区,表示要写的数据;count表示要写的数据的长度,单位是字节。如果函数执行成功,就返回实际写入数据的字节数。当有错误发生时,则返回-1,错误代码可以通过errno查看。
常见的错误代码如下:
EINTR: 此调用被信号中断
EADF: 参数fd为非有效的文件描述符,或该文件已关闭。
*/
举例:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
int fd = -1;
ssize_t size = -1;
int input = 0;
char buf[] = "boys and girls\n hi,children!"; // 要写入的字符串
char filename[] = "./input.txt";
fd = open(filename, O_RDWR | O_CREAT | O_APPEND); // 以追加的方式打开一个文件
if (-1 == fd)
printf("Open file %s failure \n", filename);
else
printf("Open file %s success \n", filename);
size = write(fd, buf, strlen(buf)); // 实际写入数据由函数返回存入size中
printf("write %d bytes to file %s\n", size, filename);
close(fd);
return 0;
}
七 设定文件偏移量
文件偏移量指的是当前文件操作位置相对于文件开始位置的偏移。当打开一个文件时,如果没有指O_APPEND参数,文件的偏移量为0。如果指定了O_APPEND参数,文件的偏移量与文件的长度相等,即文件的当前操作位置移到了末尾。
lseek函数声明如下:
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence)
// whence: 根源,来源
/*
whence代表起始位置,off_t是描述偏移量大小的类型,可以为负数。如果lseek()函数操作成功,就返回新的文件偏移量的值;如果失败,就返回-1。由于文件的偏移量可以为负值,因此判断lseek()是否操作成功时,不要使用小于0的判断,要使用是否等于-1来判断。参数offset 和 whence 的搭配如下:
whence 值为 SEEK_SET 时, offset 为相对文件开始处的值。
whence 值为 SEEK_CUR 时, offset 为相对当前位置的值。
whence 值为 SEEK_END 时, offset 为相对文件结尾的值。
*/
举例:
#include <fcntl.h>
#include <string.h>
int main(void)
{
int fd = -1;
ssize_t size = -1;
off_t offset = -1;
char buf[] = "boys";
char filename[] = "./output.txt";
fd = open(filename, O_RDWR); // 读写方式打开
if (-1 == fd)
{
printf("Open file %s fialure, fd:%d", filename, fd);
return -1;
}
offset = lseek(fd, 5, SEEK_SET); // 重新定义文件偏移量到5
if (-1 == offset) // 判断是否设置偏移量失败
{
printf("lseek file %s failure, fd:%d", filename, fd);
return -1;
}
size = write(fd, buf, strlen(buf)); // 向文件写入数据,如果原本5后面有数据,会替换后面的数据
if (size != strlen(buf))
{
printf("write file %s failure, fd:%d", filename, fd);
return -1;
}
close(fd);
return 0;
}
如果 output.txt 中的数据是:hello world,那么程序执行后,内容变为: helloboysld ,会替换其中的字符。
八 获取文件状态
stat()函数、fstat()函数和 lstat()函数都可以获得文件的状态。这些函数声明如下:
int stat(const char *path, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *path, struct stat *buf);
/*
其中,参数path是文件的路径(含文件名); filedes 是文件描述符; buf 为指向 struct stat 结构体的指针,获得的状态从这个参数中传回。当函数执行成功时返回0,执行失败返回-1。
fstat 区别于另外两个系统调用的地方在于,fstat 系统调用接收的是一个“文件描述符”,而另外两个则直接接收“文件全路径”。
stat 与 lstat 的区别: 当文件是一个符号链接时, lstat返回的是该符号链接本身的信息; 而 stat 返回的是该链接指向的文件的信息。
*/
结构体 struct stat 为一个描述文件状态的结构,包含信息如下:
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特殊设备号码
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //最后一次访问文件(读取或执行)的时间
time_t st_mtime; //最后一次修改文件(属性或权限)的时间
time_t st_ctime; //最后一次改变文件(内容)的时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //文件内容对应的块数量
};
举例:
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main(void)
{
struct stat st;
if (-1 == stat("./output.txt", &st)) // 判断是否获取成功
{
cout << "get file stat failed\n";
return -1;
}
cout << "file length:" << st.st_size << "byte" << endl; // 文件长度
cout << "mod time:" << st.st_mtime << endl; // 最后修改时间
cout << "node:" << st.st_ino << endl; // 节点
cout << "mode:" << st.st_mode << endl; // 模式
}
九 创建和删除目录项
在linux中,目录也是文件,可以称为目录文件。目录文件中存放的是文件名和对应的inode号码,统称为目录项。link 函数和 unlink 函数分别用来创建硬链接和删除硬链接。link 函数创建一个新目录项,并且增加文件的一个链接数。unlink 函数删除目录项,并且减少一个链接数。如果链接数达到0并且没有任何进程打开该文件,该文件内容才被真正的删除。如果在 unlink 之前没有 close,那么依旧可以访问文件内容。两个函数中的操作都是原子操作。总之,真正影响链接数的操作是 link、unlink、open。删除文件的真正含义是文件的链接数为0。
int link(const char *oldpath, const char *newpath);
/*
oldpath: 源文件路径名;
newpath: 新文件路径名;
当oldpath不存在或newpath存在但调用失败时返回-1,调用成功返回0。
*/
int unlink(const char *pathname);
/*
pathname: 要删除目录项的文件路径名;
如果函数执行成功就返回0,否则返回
*/
在linux中,是用inode来区分文件的,当删除一个文件的时候,系统并不一定就会释放inode节点的内容。当满足以下要求时,系统都会释放inode节点的内容:
(1)inode中记录指向该节点的硬链接数为0;
(2)没有进程打开指向该节点的文件;
unlink 函数的另一个用途就是用来创建临时文件。
如果在程序中使用open创建了一个文件后,立即使用unlink函数删除文件。由于此时函数进程正在打开该文件,因此系统不会释放该文件的inode节点,而只是删除其目录项。当进程退出时,该inode节点将会立即被释放。
临时文件可以用在进程间通信的有名管道通信中。
举例
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
struct stat st;
stat("test.txt", &st);
printf("1.link = %d \n", st.st_nlink); // link = 1
fd = open("test.txt", O_RDONLY);
stat("test.txt", &st);
printf("2.link = %d \n", st.st_nlink); // link = 1 open不影响链接数
close(fd);
stat("test.txt", &st);
printf("3.link = %d \n", st.st_nlink); // link = 1 close不影响链接数
link("test.txt", "test2.txt");
stat("test.txt", &st);
printf("4.link = %d \n", st.st_nlink); // link = 2 link后链接数加1
unlink("test2.txt");
stat("test.txt", &st);
printf("5.link = %d \n", st.st_nlink); // link = 1 unlink后链接数减1
fd = open("test.txt", O_RDONLY);
stat("test.txt", &st);
printf("6.link = %d \n", st.st_nlink); // link = 1 重新打开,链接数不变
unlink("test.txt");
// 此时使用fstat函数而非stat
// 因为unlink已经删除文件名,不能通过文件名访问,但是fd仍然是打开着的,
// 文件内容还没有被真正删除,依旧可以使用fd获得文件信息
fstat(fd, &st);
printf("7.link = %d \n", st.st_nlink); // link = 0 unlink后链接数减1
close(fd);
return 0;
}
十 文件锁定
当多个用户共同使用、操作一个文件时,Linux采用的方法就是给文件上锁,来避免共享的资源产生竞争的状态。文件锁分为 建议性锁 和 强制性锁。
建议性锁 是指给文件上锁后,只在文件上设置一个锁的标识,其它进程在对这个文件进程操作时,可以检测锁的存在。但这个锁并不能阻止其它进程对这个文件进行操作。这就好比红绿灯,当红灯亮时,告诉你不要过马路,但如果你一定要过,也拦不住你。
强制性锁 则是当给文件上锁后,其它进程要对这个文件进行不兼容的操作(比如上了读锁,另外一个进程要写)时,系统内核将阻塞后来的进程,直到第一个进程将锁解开。
值得注意的是,对文件加锁是原子性的。另外,由 fork产生的子进程不继承父进程所设置的锁。意味着,若一个进程得到一把锁,然后调用 fork,那么对于父进程获得的锁而言,子进程被视为另一个进程。对于从父进程处继承过来的任一描述符,子进程需要调用 fcntl才能获得它自己的锁。
Linux下可以用 fcntl 函数来实现文件的锁定。文件锁定在很多场合都很有用,比如为了防止进程重复启动,可以在进程启动时对 /var/run 下的 .PID文件进行锁定,这样后面的进程重复启动时,会因为无法对该文件上锁而退出。fcntl 函数不仅能对整个文件上锁,而且可以对文件的某一记录上锁,此时的锁又可以称为 记录锁。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock *lock)
/*
其中,fd 是文件描述符; cmd 是操作命令,取值如下:
F_GETLK: 根据 lock 描述,决定是否上文件锁(或记录锁)。
F_SETLK: 设置 lock 描述的文件锁(或记录锁)。
记录锁: 将文件中某一段记录锁起来。
文件锁: 将整个文件锁起来。
如果函数成功,返回0,否则返回-1,此时可以用errno查看错误代码。
fcntl函数还有其它功能和形式。
*/
如果你编写的程序或者程序新创建的进程都以一致的方式处理文件(读写前都申请一次文件(记录)锁,或者都不申请 ),这样的进程称为 合作进程,合作进程使用建议性锁是可行的。但是如果需要阻止非你创建的进程也按照一致的方式处理记录锁,就只能使用 强制性记录锁 了。强制锁 是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其它进程对其进行读写操作。
采取强制锁对性能的影响很大, fcntl 默认是建议锁, 如果想在 Linux 中 使用强制锁,则要在root权限下, 通过
mount -o mand
打开该机制。mount -o : --option 开启一些选项,选项以逗号分隔。 mount -o mand 对当前 file system 开启 mandatory lock (强制锁)
结构体 struct flock 表示一个 文件锁 或 记录锁,包含信息如下:
struct flock {
short int l_type; // 锁定的状态
short int l_whence; // 决定l_start的位置
off_t l_start; // 锁定区域的开头位置
off_t l_len; // 锁定区域的大小
pid_t l_pid; // 锁定动作的进程
};
l_type有以下3个选项:
F_RDLCK: 共享锁(也称读取锁),只读用,多个进程可以同时建立读取锁
F_WRLCK: 独占锁(也称写入锁),在任何时刻只有一个进程建立写入锁。
F_UNLCK: 解除锁定。
l_whence 为之前定义的类型
l_start 为相对开始偏移量,相对于l_whence而言。
l_len: 加锁的长度, 0 为到文件末尾。
l_pid: 当前操作文件的进程ID号。
举例:
#include <fcntl.h>
#include <sys/stat.h>
#include <error.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
struct flock lock; // 定义文件锁
int res, fd = open("myfile.txt", O_RDWR | O_CREAT,0644);
if (fd > 0)
{
lock.l_type = F_WRLCK; // 独占锁
lock.l_whence = SEEK_SET; // 文件开始位置
lock.l_start = 0;
lock.l_len = 0; // 锁定到文件末尾
lock.l_pid = getpid();
res = fcntl(fd, F_SETLK, &lock);
printf("return value of fcntl=%d\n", res);
while(true);
}
return 0;
}
现在程序处于死循环中,一直在运行了。此时这个终端被当前进程独占,重新打开一个终端,修改 myfile.txt,是可以操作成功的。说明,在建议锁锁住的情况,其它的进程的确可以修改被锁定文件的。因此,建议锁只适用于 合作进程。
如果使用 mount -o mand 选项 重新挂载 当前文件所在的文件系统 ,就可以实现 强制性锁。 此时,其它进程是不能修改 myfile.txt的。
十一 建立文件和内存映射
所谓的文件和内存映射,就是将普通文件映射到内存中,普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用 read 或 write 等操作。系统提供了函数 mmap 将普通文件映射到内存中,该函数的声明如下:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
/*
其中,start 为映射区的起始地址,通常为 NULL 或者 0,表示 由系统自己决定映射到什么地址;
length 表示映射数据的长度,即文件需要映射到内存中的数据的大小;
prot 表示映射区保护方式,取下列值或者它们的组合:
PROTOT_EXEC: 表示映射区域可执行
PROT_READ: 表示映射域可读取
PROT_WRITE: 表示映射域可写入
PROT_NONE: 表示映射域可写入
参数flags用来指定映射对象的类型、映射选项和映射页是否可以共享。它的值是一个或者多个位的组合:
MAP_FIXED:
如果参数start指定了用于需要映射至的地址,而所指的地址无法成功建立映射,则映射失败。通常不推荐使用此设置,而将start设为0,由系统自动选取映射地址。
MAP_SHARED:
共享的映射区域,映射区域允许其他进程共享,对此映射区域写入数据将会写入到原来的文件中。
MAP_PRIVATE:
当对映射区域进行写入操作时会产生一个映射文件的复制,即写入复制(copy on write),而读操作不会影响此复制。对此映射区的修改不会写回原来的文件,即不会影响原来文件的内容。
MAP_ANONYMOUS:
建立匿映射。此时会忽略参数fd,不涉及文件,而且映射区域无法与其他进程共享。
MAP_DENYWRITE:
对文件的写入操作将被禁止,只能通过对此映射区打哈哈物方式实现对文件的操作,不允许直接对文件进行操作。
MAP_LOCKED:
将映射区锁定,此区域不会被虚拟内存重置。
注意:flags参数必须是MAP_PRIVATE或者MAP_SHARED二者之一的类型。
mmap()映射后, 让用户程序直接访问设备内存,相比较在用户空间和内核空间互相复制数据,效率更高,在要求高性能的应用中比较常见。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。
页面大小:内存页面大小,可以通过linux命令:getconf PAGESIZE 查看
*/
举例:
#include <sys/mman.h> // for mmap and munmap
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
char *mapped_mem;
int flength = 1024;
void *start_addr = 0;
fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
flength = lseek(fd, 1, SEEK_END);
write(fd, "\0", 1); // 在文件最后添加一个空字符,方便printf()打印
lseek(fd, 0, SEEK_SET);
mapped_mem = (char*)mmap(start_addr,
flength,
PROT_READ, // 允许读
MAP_PRIVATE, // 不允许其他进程访问此内存区域
fd,
0);
// 打印映射区域
printf("%s\n", mapped_mem);
close(fd);
munmap(mapped_mem, flength); //munmap()函数的作用是取消mmap()函数的映射关系。
return 0;
}
修改文件的内存映射:
#include <sys/mman.h> // for mmap and munmap
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h> // for memcpy
int main(int argc, char **argv) // 为了方便打印,最好传递一个文本文件
{
int fd;
char *mapped_mem, *p;
int flength = 1024;
void *start_addr = 0;
fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
flength = lseek(fd, 1, SEEK_END);
write(fd, "\0", 1); // 在文件最后添加一个空字符,方便printf()打印
lseek(fd, 0, SEEK_SET);
start_addr = (void*)0x8000;
mapped_mem = (char*)mmap(start_addr,
flength,
PROT_READ | PROT_WRITE, // 允许写入
MAP_SHARED, // 允许其他进程访问此内存区域
fd,
0);
// 打印映射区域
printf("%s\n", mapped_mem);
// strstr返回字符串中首次出现子串的地址
while( (p = strstr(mapped_mem, "hello"))) // 假设文本中是包含有hello的
{
memcpy(p, "Linux", 5);
p += 5;
}
close(fd);
munmap(mapped_mem, flength); //munmap()函数的作用是取消mmap()函数的映射关系。
return 0;
}
输出结果
假设原本 output.txt中的内容为:
hello
boy
执行程序后,output.txt内容为:
Linux
boy
十二 mmap 和 共享内存对比
共享内存允许两个或多个进程共享一个给定的储存区,因为数据不需要来回复制,所以是最快的一种进程间通信。共享内存可以通过 mmap()映射普通文件机制 来实现,也可以通过 系统V共享内存机制 来实现。
系统V共享内存:指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。
mmap机制:就是在磁盘上建立一个文件,每个进程存储器里面单独开辟一个空间来进行映射。如果是多进程,那么对实际的物理存储(主存)消耗不会太大。mmap保存到实际硬盘,实际存储并没有反映到请在上。优点是储存量可以很大(多于主存),缺点是进程间读取和写入速度要比主存的要慢。
shm机制:每个进程的共享内存都直接映射到实际物理存储器里面。shm保存到物理存储器(主存),实际的存储量直接反映到主存一。优点是进程间访问的速度(读写)比磁盘要快,缺点是存储量不能非常大(多于主存)。