Linux文件I/O编程

2023-11-07


   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种编程方式:

  1. 调用C库中文件的IO函数,如 fopen、fread/fwrite、fclose等。
  2. 使用Linux的系统调用。
  3. 使用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保存到物理存储器(主存),实际的存储量直接反映到主存一。优点是进程间访问的速度(读写)比磁盘要快,缺点是存储量不能非常大(多于主存)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux文件I/O编程 的相关文章

随机推荐

  • CTF学习笔记——Include&Ping Ping Ping

    一 ACTF2020 新生赛 Include 1 题目 2 解题步骤 点进去看了一下 根据题目猜测 应该是和php的文件包含漏洞有关 尝试了一下显示phpinfo 意料之中的失败了 看wp才了解到 这是一道伪协议的题目 然后翻了一下之前的博
  • layui如何动态上传文件

    HTML div class layui form item od customer src div
  • csu 1811 Tree Intersection 2016湖南省赛 I

    Problem acm csu edu cn csuoj problemset problem pid 1811 vjudge net contest 161962 problem I Reference blog csdn net qwb
  • Android 计时器Chronometer 使用及源码分析,常见移动app开发框架

    主界面布局文件 仅保留Chronometer相关布局
  • 基于协同过滤的用户推荐的java例子

    基于协同过滤的用户推荐的java例子 基于用户的协同过滤推荐算法 1 基于用户的协同过滤推荐算法 2 基于用户的协同过滤推荐算法通过寻找与目标用户具有相似评分的邻居用户 通过查找邻居用户喜欢的项目 推测目标用户也具有相同的喜好 基于用户的协
  • Linux环境下如何杀死僵尸进程

    我们在使用top命令查看主机性能的的时候会在第二行会查看到有zombie关键字 此关键字代表僵尸进程的意思 僵尸进程 当进程退出时 它向父进程发送一个SIGCHLD信号 默认情况下总是忽略SIGCHLD信号 此时进程状态一直保留在内存中 直
  • K8S 学习四(POD详解)

    POD结构 每个pod中都可以包含一个或者多个容器 这些容器可以分为两类 1 用户程序所在的容器 数量可多可少 上图的第一 第二层 2 Pause容器 这是每个pod都会有的一个根容器 它的作用有两个 2 1 可以以它为依据 评估整个Pod
  • cas单点登录-自定义登录验证(四)

    我们在使用SSO单点登录的时候不只是验证一下用户名和密码是否一致 有时候还需要验证一些别的校验 那么这一张讲一下如何自定义验证器 自定义验证很重要 因为我们后续的很多功能 都是基于自定义验证 CAS服务器的org apereo cas au
  • 在浏览器地址栏键入URL按下回车之后会经历什么?

    在浏览器地址栏键入URL按下回车之后主要会经历以下7个步骤 1 查找浏览器缓存 如果查找到缓存中有我们URL对应的文件 则判断是否命中强缓存 如果命中直接读取使用即可 如果强缓存没有命中 判断协商缓存是否命中 但协商缓存不论是否命中都会发送
  • es部署--生产环境--01--es单机

    es部署 生产环境 01 es单机 前提 使用hd用户登陆 完成基础环境搭建 https blog csdn net zhou920786312 article details 118212302 1 资源下载 elasticsearch
  • Asp.net页面之间传递参数的几种方法

    1 使用QueryString变量 QueryString是一种非常简单的传值方式 他可以将传送的值显示在浏览器的地址栏中 如果是传递一个或多个安全性要求不高或是结构简单的数值时 可以使用这个方法 但是对于传递数组或对象的话 就不能用这个方
  • 【C++】-- 哈希(上万字详细配图配代码从执行一步步讲解)

    目录 哈希 常见哈希函数 除留余数法 哈希冲突 哈希冲突解决 闭散列 a 线性探测 插入 查找 删除 线性探测的实现代码 b 二次探测 二次探测的实现 开散列 开散列实现 插入 查找 删除 析构函数 代码汇总 哈希 常见哈希函数 直接定址法
  • 2017第八届Java A组蓝桥杯省赛真题第九题:分巧克力

    第九题 分巧克力 儿童节那天有K位小朋友到小明家做客 小明拿出了珍藏的巧克力招待小朋友们 小明一共有N块巧克力 其中第i块是Hi x Wi的方格组成的长方形 为了公平起见 小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们 切出的巧克力
  • c++ 数据结构——链表

    1 链表概念 暂略 2 栈的相关题目 2 1 leetcode 237 Delete Node in a Linked List 注意 这个题没有给head Definition for singly linked list struct
  • mysatis中子查询剖析

    mybatis中查询代码是这样写的
  • 为什么要对基带信号进行脉冲成型【转载】

    数字信号在传输过程中受到叠加干扰与噪声 从而出现波形失真 瑞典科学家哈利 奈奎斯特在1928 年为解决电报传输问题提出了数字波形在无噪声线性信道上传输时的无失真条件 称为奈奎斯特准则 其中奈奎斯特第一准则是抽样点无失真准则 或无码间串扰 I
  • Java8 Stream学习笔记

    一 什么是Stream流 WHAT 在Java中 集合和数组是我们经常会用到的数据结构 需要经常对他们做增 删 改 查 聚合 统计 过滤等操作 相比之下 关系型数据库中也同样有这些操作 但是在Java 8之前 集合和数组的处理并不是很便捷
  • php微信公众号code获取不到,微信公众号调取用户信息,遇到invalid code的问题

    使用了php php部分如下 code GET code userinfo getUserInfo code function getUserInfo code appid 1111111111 appsecret 111111111111
  • Redis与数据库一致性问题分析

    缓存已经在项目中被广泛使用 在读取缓存方面 大家没啥疑问 都是按照下图的流程来进行业务操作 但是在更新缓存方面 对于更新完数据库 是更新缓存呢 还是删除缓存 又或者是先删除缓存 再更新数据库 其实大家存在很大的争议 所以参考了网上一些资料对
  • Linux文件I/O编程

    文章目录 一 文件描述符 二 打开文件 三 创建文件 四 关闭文件 五 读取文件 六 向文件写入数据 七 设定文件偏移量 八 获取文件状态 九 创建和删除目录项 十 文件锁定 十一 建立文件和内存映射 十二 mmap 和 共享内存对比 I