Linux多进程编程(上)

一、虚拟内存与物理映射


二、PCB

cpp 复制代码
PCB进程控制块:
进程id
文件描述符表
进程状态:初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码
信号相关信息资源。
用户id和组id

三、fork、getpid、getppid

cpp 复制代码
//子进程创建成功返回0,父进程返回子进程pid
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	printf("before fork-1-\n");
	printf("before fork-2-\n");
	printf("before fork-3-\n");
	printf("before fork-4-\n");

	pid_t pid = fork();
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if (pid == 0) {
		printf("---child is created\n");
	}
	else if (pid > 0)
	{
		printf("---parent process: my child is %d\n", pid);
	}

	printf("-----------------end of file\n");

	return 0;
}

//运行结果如下
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	printf("before fork-1-\n");
	printf("before fork-2-\n");
	printf("before fork-3-\n");
	printf("before fork-4-\n");

	pid_t pid = fork();
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if (pid == 0) {
		printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
	}
	else if (pid > 0)
	{
		printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
	}

	printf("-----------------end of file\n");

	return 0;
}

//运行结果如下
//7876是bash进程

四、循环创建多个子进程

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	for (i = 0; i < 5; i++) {
		pid_t pid = fork();
		if (pid == -1) {
			perror("fork error");
			exit(1);
		}
		else if (pid == 0) {
			printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
		}
		else if (pid > 0)
		{
			printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
		}
	}

	return 0;
}

//i=2子进程如下
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		printf("I'm parent\n");
	}
	else {
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//出现下面这种情况是因为bash进程优先抢到CPU,子进程没抢到
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		sleep(5);
		printf("I'm parent\n");
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//这样保证了子进程按顺序执行,父进程也是放到最后一个
cpp 复制代码
fork函数:
父进程返回子进程pid。子进程返回0.

父子进程相同:
刚fork后。data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式

父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集

父子进程共享:
读时共享、写时复制。-----------  全局变量。
1.文件描述符 2.mmap映射区。

五、父子进程gdb调试

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		sleep(5);
		printf("I'm parent\n");
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//这样保证了子进程按顺序执行,父进程也是放到最后一个
bash 复制代码
//调试子进程

gdb test(上述代码文件名)

list 1

l

b 12(在for循环处创建断点)

r(运行)

n(next)

set follow-fork-mode child(切换到子进程)

n

n
bash 复制代码
//调试父进程

gdb test

l 1

l

b 12

r

set follow-fork-mode parent

n

n

n

n

n

//多个n创建5个子进程

六、exec

cpp 复制代码
exec所干的事就是将exec函数里的内容覆盖子进程原来fork的内容。

七、execlp和execl

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char *argv[]){
    pid_t pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }
    else if(pid == 0){        //子进程
        execlp("ls", "ls", "-l", "-h", NULL);    //第一个逗号之后的ls是argv[0]
        execlp("date", "date", NULL);
        execl("./a.out", "./a.out", NULL);    //执行相对路径下的a.out文件
        perror("exec error");                 //如果execl或execlp出错就会执行以下内容
        exit(1);                              //因为execl或execlp执行成功没有返回值,不返回
    }
    else if(pid > 0){
        sleep(1);
        printf("I'm parent: %d\n", getpid());
    }
    
    return 0;
}

//子进程会做出ls、date命令和执行a.out文件,下图只展示一个功能

八、孤儿进程和僵尸进程

cpp 复制代码
//孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程被init进程领养。

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid;
    pid = fork();

    if(pid == 0){
        while(1){
            printf("I'm child, my parent pid = %d\n", getppid());
            sleep(1);
        }
    }
    else if(pid > 0){
        printf("I'm parent, my pid = %d\n", getpid());
        sleep(9);
        printf("-----------parent is going to die------------\n");
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

//如下图,当父进程执行完退出后子进程变为孤儿进程,他的父进程变为pid为1721 init父进程
//用两次ps ajx命令可以查看子进程前后父进程pid的变化
cpp 复制代码
//僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid;
    pid = fork();
    
    if(pid == 0){
        printf("child, my parent = %d, go to sleep 10s\n", getppid());
        sleep(10);
        printf("----child die----\n");
    }
    else if(pid > 0){
        while(1){
            printf("I'm parent, pid = %d, myson = %d\n", getpid(), pid);
            sleep(1);
        }
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

//当子进程结束后,子进程变成僵尸进程,只需将父进程kill就解决这个问题
//kill -9 3464(父进程pid)

九、wait回收子进程

cpp 复制代码
wait函数:回收子进程退出资源
作用1:阻塞等待子进程退出
作用2:清理子进程残留在内核的pcb资源
作用3:通过传出参数,得到子进程结束状态
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid, wpid;
    int status;
    
    pid = fork();
    if(pid == 0){
        printf("child, my id = %d, go to sleep 10s\n", getppid());
        sleep(10);
        printf("---------child die---------\n");
    }
    else if(pid > 0){
        wpid = wait(&status);        //status是个传出参数,将子进程状态传出来
        if(wpid == -1){
            perror("wait error");
            exit(1);
        }
        printf("---------parent wait finish: %d\n", wpid);
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

十、子进程退出和终止

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid, wpid;
    int status;

    pid = fork();
    if(pid == 0){
        printf("---child, my id = %d, go to sleep 10s\n", getpid());
        sleep(10);
        printf("---child die---\n");
        return 73;
    }
    else if(pid > 0){
        //wpid = wait(NULL)        //不关心子进程结束原因
        wpid = wait(&status);    //如果子进程未终止,父进程阻塞(等待)在这个函数上
        if (wpid == -1){
            perror("wait error");
            exit(1);
        }
        if (WIFEXITED(status)){    //为真,说明子进程正常终止
            printf("child exit with %d\n", WEXITSTATUS(status));
        }
        if (WIFSIGNALED(status)){    //为真,说明子进程是被信号终止
            printf("child kill with signal %d\n", WTERMSIG(status));
        }

        printf("parent wait finish: %d\n", wpid);
    }
    else{
        perror("fork");
        return 1;
    }
    
    return 0;
}

//下图第一个和第三个案例是被信号(9,11)终止,第二个是正常超时退出(退出值73)

十一、waitpid回收子进程

cpp 复制代码
waitpid函数:
pid_t waitpid(pid_t pid, int *status, int options)

参数:
pid:指定回收的子进程pid, > 0:待回收的子进程pid, -1:任意子进程, 0:同组的子进程。
status:(传出)回收进程的状态。
options:WNOHANG指定回收方式为非阻塞。

返回值:
> 0:表示成功回收的子进程pid
0:函数调用时,参数3指定了 WNOHANG,并且,没有子进程结束。
-1:失败。errno
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		//sleep(5);
        wait(NULL);    //一次wait/waitpit函数调用,只能回收一个子进程
        wpid = waitpid(-1, NULL, WNOHANG);    //-1回收任意子进程,相当于wait
        if(wpid == -1){
            perror("waitpid error");
            exit(1);
        }
		printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//由于父进程没有sleep,父进程先运行,但此时没有子进程运行,所以没有回收子进程
//WNOHANG表示非阻塞,没回收就直接返回0
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid, tmpid;

	for (i = 0; i < 5; i++) {
        pid = fork();
		if (pid == 0) {    //是子进程,退出
			break;
		}
        if(i == 2){        //没有被break就是父进程,pid是子进程的pid
            tmpid = pid;
            printf("pid = %d\n", tmpid);
        }
	}

	if (i == 5) {    //父进程
		//sleep(5);
        //wpid = waitpid(tmpid, NULL, WNOHANG);    //指定一个进程回收,不阻塞
        wpid = waitpid(tmpid, NULL, 0);            //指定一个进程回收,阻塞
        
        if(wpid == -1){
            perror("waitpid error");
            exit(1);
        }
		printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {        //子进程
		sleep(i);
		printf("I'm %dth child, pid = %d\n", i + 1, getpid());
	}

	return 0;
}

//有两种指定回收子进程方式,第一种是sleep+WNOHANG非阻塞,第二种是用参数0来阻塞
//第一张图是sleep的,等子进程全都退出父进程再回收
//第二张图是阻塞的,等待第三个子进程执行完立马回收

十二、waitpid回收多个子进程

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid;

	for (i = 0; i < 5; i++) {
        pid = fork();
		if (pid == 0) {    //是子进程,退出
			break;
		}
	}

	if (i == 5) {    //父进程
        //第二个参数NULL表示不关心子进程状态,0表示阻塞
        /*while((wpid = waitpid(-1, NULL, 0))){ 
		    printf("wait child:%d\n", wpid);
        }*/
        
        //非阻塞方式回收
        while((wpid = waitpid(-1, NULL, WNOHANG)) != -1){
            if(wpid > 0){
                printf("wait child:%d\n", wpid); 
            }
            else if(wpid == 0){
                sleep(1);
                continue;
            }
        }

        printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {        //子进程
		sleep(i);
		printf("I'm %dth child, pid = %d\n", i + 1, getpid());
	}

	return 0;
}

十三、进程间通信常见方式

cpp 复制代码
英文简称:IPC,InterProcess Communication
cpp 复制代码
四种进程间通信方式:
1.管道(使用最简单,得有血缘关系,如父子关系)
2.信号(开销最小)
3.共享映射区(可应用于无血缘关系的进程间)
4.本地套接字(最稳定,较复杂,一般用于网络)

十四、管道通信特性

cpp 复制代码
管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现。

特质:
1.伪文件(文件占用磁盘空间,伪文件占用内存空间)
2.管道中的数据只能一次读取。
3.数据在管道中,只能单向流动。

局限性:
1.自己写,不能自己读。
2.数据不可以反复读。
3.半双工通信。
4.血缘关系进程间可用。

十五、管道函数pipe

cpp 复制代码
pipe函数:创建,并打开管道。
int pipe(int fd[2]);

参数:
fd[0]:读端。
fd[1]:写端。

返回值:
成功:0
失败:-1 errno
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        write(fd[1], str, strlen(str));
        sleep(1);        //保证父进程不先退出
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

十六、管道读写行为

cpp 复制代码
读管道:
1.管道中有数据,read返回实际读到的字节数。
2.管道中无数据:
    (1)管道写端被全部关闭,read返回0(类似读到文件结尾)。
    (2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据到达)

写管道:
1.管道读端全部被关闭,进程异常终止(SIGPIPE信号导致的)
2.管道读端没有全部关闭:
    (1)管道已满,write阻塞等待。
    (2)管道未满,write将数据写入,并返回实际写入的字节数。
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        sleep(3);
        write(fd[1], str, strlen(str));
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

//等待3s父进程写数据
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        sleep(3);
        //write(fd[1], str, strlen(str));
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        printf("child read ret = %d\n", ret);
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

//父进程未写入数据,返回0

十七、用管道实现ls | wc -l

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char* argv[]) {
    int fd[2];
    int ret;
    pid_t pid;

    ret = pipe(fd);
    if (ret == -1) {
        sys_err("pipe error");
    }

    pid = fork();
    if (pid == -1) {
        sys_err("fork error");
    }
    else if (pid > 0) {
        //父进程来读,这样就不会先于子进程执行,因为会阻塞等待子进程写入
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL); //执行成功就不回来了
        sys_err("execlp wc error");
    }
    else if (pid == 0) {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
        sys_err("execlp ls error");
    }

    return 0;
}

//这个程序可以输出当前目录文件个数(ls | wc -l)
//注意 | 是管道符

十八、兄弟进程实现ls | wc -l

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char* argv[]) {
    int fd[2];
    int ret, i;
    pid_t pid;

    ret = pipe(fd);
    if (ret == -1) {
        sys_err("pipe error");
    }

    for (int i = 0; i < 2; i++) {
        pid = fork();
        if (pid == -1) {
            sys_err("fork error");
        }

        if (pid == 0) {
            break;
        }
    }

    if (i == 2) {
        //父子进程都公用一个管道,必须保证管道单向流动,关闭父进程读写端
        close(fd[0]);
        close(fd[1]);
        wait(NULL);
        wait(NULL);
    }
    else if (i == 0) {  //兄长进程
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
        sys_err("execlp ls error");
    }
    else if (i == 1) {  //小弟进程
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);
        sys_err("execlp wc error");
    }

    return 0;
}

十九、管道的一写端多读端

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>

int main(void) {
	pid_t pid;
	int fd[2], i, n;
	char buf[1024];
	int ret = pipe(fd);

	if (ret == -1) {
		perror("pipe error");
		exit(1);
	}

	for (i = 0; i < 2; i++) {
		if ((pid = fork()) == 0) {
			break;
		}
		else if (pid == -1) {
			perror("pipe error");
			exit(1);
		}
	}

	if (i == 0) {
		close(fd[0]);
		write(fd[1], "1.hello\n", strlen("1.hello\n"));
	}
	else if (i == 1) {
		close(fd[0]);
		write(fd[1], "2.world\n", strlen("2.world\n"));
	}
	else {
		close(fd[1]);	//父进程关闭写端,留读端读取数据
		sleep(1);		//保证两个子进程都写完再回收
		n = read(fd[0], buf, 1024);	//从管道中读数据
		write(STDOUT_FILENO, buf, n);
		for (i = 0; i < 2; i++) {
			wait(NULL);	//两个儿子wait两次
		}	
	}

	return 0;
}

//这个程序父进程读,两个子进程写,由于按循环顺序i==0的子进程概率先执行,所以打印hello world
//如果想保证顺序,两个子进程用sleep来控制先后

二十、命名管道fifo创建

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

int main(int argc, char *argv[]) {
	int ret = mkfifo("myfifo", 0644);	//创建管道myfifo		rw-rw-r--
	if (ret == -1) {
		sys_err("mkfifo error");
	}

	return 0;
}

二十一、fifo实现非血缘关系进程通信

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/type.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

void sys_err(char* str) {
	perror(str);
	exit(1);
}

int main() {
	int fd, i;
	char buf[4096];

	if (argc < 2) {
		printf("Enter like this: ./a.out fifoname\n");
		return -1;
	}
	
	fd = open(argv[1], O_WRONLY);
	if (fd < 0) {
		sys_err("open error");
	}

	i = 0;
	while (1) {
		sprintf(buf, "hello happygame %d\n", i++);
		write(fd, buf, strlen(buf));
		sleep(1);
	}
	close(fd);

	return 0;
}

//以上是写端的代码
//执行程序要输入fifo命名管道参数,如myfifo
cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/type.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

void sys_err(char* str) {
	perror(str);
	exit(1);
}

int main() {
	int fd, len;
	char buf[4096];

	if (argc < 2) {
		printf("Enter like this: ./a.out fifoname\n");
		return -1;
	}

	fd = open(argv[1], O_WRONLY);
	if (fd < 0) {
		sys_err("open error");
	}

	while (1) {
		len = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, len);
		sleep(3);
	}
	close(fd);

	return 0;
}

//以上是读端的代码
//usleep是微秒睡眠
//执行程序要输入fifo命名管道参数
cpp 复制代码
以上实现了一读端多写端

如果是一写端多读端就会出现接收数据混乱(如下图)

两者都需要多个bash窗口来实现,而且各个进程毫无血缘关系

二十二、mmap、munmap函数

cpp 复制代码
存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。
这个映射工作可以通过mmap函数来实现
cpp 复制代码
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset):
创建共享内存映射
参数:
    addr:指定映射区的首地址。通常传NULL,表示让系统自动分配
    length:共享内存映射区的大小。(<= 文件的实际大小)
    prot:共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ | PROT_WRITE
    flags:标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE
    fd:用于创建共享内存映射区的那个文件的文件描述符。
    offset:默认0,表示映射文件全部。偏移位置。需是4K的整数倍。

返回值:
    成功:映射区的首地址。
    失败:MAP_FAILED,errno
cpp 复制代码
int munmap (void *addr, size_t length);    
释放映射区。
addr:mmap的返回值
length:大小

二十三、mmap建立映射区

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

int main(int argc, char* argv[]) {
	char* p = NULL;
	int fd;

	fd = open("testmap", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		sys_err("open error");
	}

	//lseek(fd, 10, SEEK_END);
	//write(fd, "\0", 1);
	ftruncate(fd, 20);	//这个函数扩展空间可顶替上两行,需要写权限才能扩展
	int len = lseek(fd, 0, SEEK_END);

	p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		sys_err("mmap error");
	}

	//使用p对文件进行读写操作
	strcpy(p, "hello mmap");	//写操作
	printf("---%s---\n", p);	//读操作

	int ret = munmap(p, len);	//释放映射区
	if (ret == -1) {
		sys_err("munmap error");
	}

    close(fd);

	return 0;
}

//如下图:读出了---hello mmap,同时在testmap文件里面写入了hellommap

二十四、mmap使用注意事项

cpp 复制代码
使用注意事项:
1.用于创建映射区的文件大小为0,实际指定非0大小创建映射区,出"总线错误"。

2.用于创建映射区的文件大小为0,实际指定0大小创建映射区,出"无效参数"错误。

3.用于创建映射区的文件读写属性为只读。映射区属性为读、写。出"无效参数"错误。

4.创建映射区需要读文件,要read权限。
权限为共享时:mmap的读写权限应 <= 文件的open权限,但不能两个都是写。
(接上:最好两者都有读写权限,因为文件有写权限才能ftruncate扩展空间,映射区有写权限才能strcpy)。

5.文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。
(接上:在创建完映射区后马上close(fd)无影响)

6.offset必须是4096的整数倍。(MMU映射的最小单位4k)

7.对申请的映射区内存,不能越界访问。

8.munmap用于释放的地址,必须是mmap申请返回的地址。

9.映射区访问权限为"私有" MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。

10.映射区访问权限为"私有" MAP_PRIVATE,只需要open文件时,有读权限,用于创建映射区即可。

mmap函数的保险调用方式:
    1. fd = open("文件名", O_RDWR);
    2.mmap(NULL, 有效文件大小, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.


二十五、父子进程间mmap通信

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int var = 100;

int main(void) {
	int* p;
	pid_t pid;

	int fd;
	fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd < 0) {
		perror("open error");
		exit(1);
	}
	ftruncate(fd, 4);

	p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap error");
		exit(1);
	}
	close(fd);		//映射区创建完毕,即可关闭文件

	pid = fork();
	if (pid == 0) {
		*p = 2000;	//写共享文件
		var = 1000;
		printf("child, *p = %d, var = %d\n", *p, var);
	}
	else {
		sleep(1);
		printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件
		wait(NULL);

		int ret = munmap(p, 4);		//释放映射区
		if (ret == -1) {
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}

//执行结果如下图:
//var值不同是因为变量读时共享,写时覆盖(相当于父子两份各自的数据,互不干涉)
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int var = 100;

int main(void) {
	int* p;
	pid_t pid;

	int fd;
	fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd < 0) {
		perror("open error");
		exit(1);
	}
	ftruncate(fd, 4);

	//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap error");
		exit(1);
	}
	close(fd);		//映射区创建完毕,即可关闭文件

	pid = fork();
	if (pid == 0) {
		*p = 7000;	//写共享文件
		var = 1000;
		printf("child, *p = %d, var = %d\n", *p, var);
	}
	else {
		sleep(1);
		printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件
		wait(NULL);

		int ret = munmap(p, 4);		//释放映射区
		if (ret == -1) {
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}

//执行结果如下图:
//由于mmap权限是MAP_PRIVATE,所以父子的p值不同,不共用

二十六、无血缘关系进程mmap通信

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#include<errno.h>

struct student {
	int id;
	char name[256];
	int age;
};

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

int main(int argc, char* argv[]) {
	struct student stu = { 1, "xiaoning", 18 };
	struct student* p;
	int fd;

	fd = open("test_map", O_RDWR | O_CREAT | O_TRUNC, 0664);    //trunc写一次清空一次
	if (fd = -1) {
		sys_err("open error");
	}

	ftruncate(fd, sizeof(stu));

	p = mmap(NULL, sizeof(stu), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		sys_err("mmap error");
	}
	close(fd);

	while (1) {
		memcpy(p, &stu, sizeof(stu));
		stu.id++;
		sleep(1);
	}

	munmap(p, sizeof(stu));

	return 0;
}

//写端进程
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#include<errno.h>

struct student {
	int id;
	char name[256];
	int age;
};

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

int main(int argc, char* argv[]) {
	struct student stu;
	struct student* p;
	int fd;

	fd = open("test_map", O_RDONLY);
	if (fd = -1) {
		sys_err("open error");
	}

	p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		sys_err("mmap error");
	}
	close(fd);

	while (1) {
		printf("id = %d, name = %s, age = %d\n", p->id, p->name, p->age);
		sleep(1);
	}

	munmap(p, sizeof(stu));

	return 0;
}

//读端代码
//由于先打开写端,已经写入几个数据,读的时候已经错过了前面的数据
cpp 复制代码
无血缘关系进程间通信。
map:数据可以重复读取。
fifo:数据只能一次读取。

例如:mmap写端进程sleep(2),读端进程sleep(1),
读端会读出两个重复的数据,因为1s写端还没有新的数据写入

二十七、mmap匿名映射区

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int var = 100;

int main(void) {
	int* p;
	pid_t pid;

	int fd;
	fd = open("temp", O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd < 0) {
		perror("open error");
		exit(1);
	}
	int ret = unlink("temp");
	if (ret == -1) {
		perror("unlink error");
		exit(1);
	}
	ftruncate(fd, 4);

	p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap error");
		exit(1);
	}
	close(fd);		//映射区创建完毕,即可关闭文件

	pid = fork();
	if (pid == 0) {
		*p = 7000;	//写共享文件
		var = 1000;
		printf("child, *p = %d, var = %d\n", *p, var);
	}
	else {
		sleep(1);
		printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件
		wait(NULL);

		int ret = munmap(p, 4);		//释放映射区
		if (ret == -1) {
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}

//unlink使得temp文件不存在,通信时不需要额外创建一个通信文件
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int var = 100;

int main(void) {
	int* p;
	pid_t pid;

    //想指定多大就多大
	p = (int*)mmap(NULL, 40, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	//p = (int*)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap error");
		exit(1);
	}

	pid = fork();
	if (pid == 0) {
		*p = 7000;	//写共享文件
		var = 1000;
		printf("child, *p = %d, var = %d\n", *p, var);
	}
	else {
		sleep(1);
		printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件
		wait(NULL);

		int ret = munmap(p, 4);		//释放映射区
		if (ret == -1) {
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int var = 100;

int main(void) {
	int* p;
	pid_t pid;

	int fd = open("dev/zero", O_RDWR);	//用于更早的unix操作系统,因为它没有ANONYMOUS

	p = (int*)mmap(NULL, 490, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap error");
		exit(1);
	}

	pid = fork();
	if (pid == 0) {
		*p = 7000;	//写共享文件
		var = 1000;
		printf("child, *p = %d, var = %d\n", *p, var);
	}
	else {
		sleep(1);
		printf("parent, *p = %d, var = %d\n", *p, var);	//读共享文件
		wait(NULL);

		int ret = munmap(p, 4);		//释放映射区
		if (ret == -1) {
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}
cpp 复制代码
注意:以上所有匿名映射区只适用于父子进程,无血缘关系进程如兄弟进程不行,
因为父子进程共享fd文件描述符和mmap映射区
相关推荐
半梦半醒*2 小时前
在Linux中部署tomcat
java·linux·运维·服务器·centos·tomcat
半梦半醒*2 小时前
haproxy负载均衡
linux·运维·服务器·centos·负载均衡
Dovis(誓平步青云)3 小时前
《Linux 指令实战进阶:从终端新手到 shell 驾驭者的技术跃迁(第三篇)》
linux·运维·服务器
東雪蓮☆3 小时前
Redis 三种服务架构详解:主从复制、哨兵模式与集群
linux·运维·数据库·redis
喝呜昂_黄3 小时前
【 嵌入式Linux应用开发项目 | Rockit + FFmpeg+ Nginx】基于泰山派的IPC网络摄像头
linux·c语言·nginx·ffmpeg
Suger9994 小时前
CentOS中设置yum源
linux·运维·centos
qziovv4 小时前
vim操作
linux·编辑器·vim
我好饿14 小时前
elk日志系统
linux·log
Nimsolax4 小时前
Linux线程互斥与同步
linux