Linux多进程编程(上)
一、虚拟内存与物理映射
二、PCB
PCB进程控制块:
进程id
文件描述符表
进程状态:初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码
信号相关信息资源。
用户id和组id
三、fork、getpid、getppid
//子进程创建成功返回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;
}//运行结果如下
#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进程
四、循环创建多个子进程
#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子进程如下
#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,子进程没抢到
#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;
}//这样保证了子进程按顺序执行,父进程也是放到最后一个
fork函数:
父进程返回子进程pid。子进程返回0.父子进程相同:
刚fork后。data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集父子进程共享:
读时共享、写时复制。----------- 全局变量。
1.文件描述符 2.mmap映射区。
五、父子进程gdb调试
#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;
}//这样保证了子进程按顺序执行,父进程也是放到最后一个
//调试子进程gdb test(上述代码文件名)list 1lb 12(在for循环处创建断点)r(运行)n(next)set follow-fork-mode child(切换到子进程)nn
//调试父进程gdb testl 1lb 12rset follow-fork-mode parentnnnnn//多个n创建5个子进程
六、exec
exec所干的事就是将exec函数里的内容覆盖子进程原来fork的内容。
七、execlp和execl
#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文件,下图只展示一个功能
八、孤儿进程和僵尸进程
//孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程被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的变化
//僵尸进程:进程终止,父进程尚未回收,子进程残留资源(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回收子进程
wait函数:回收子进程退出资源
作用1:阻塞等待子进程退出
作用2:清理子进程残留在内核的pcb资源
作用3:通过传出参数,得到子进程结束状态
#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;
}
十、子进程退出和终止
#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回收子进程
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
#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回收任意子进程,相当于waitif(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
#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是子进程的pidtmpid = 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回收多个子进程
#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;
}
十三、进程间通信常见方式
英文简称:IPC,InterProcess Communication
四种进程间通信方式:
1.管道(使用最简单,得有血缘关系,如父子关系)
2.信号(开销最小)
3.共享映射区(可应用于无血缘关系的进程间)
4.本地套接字(最稳定,较复杂,一般用于网络)
十四、管道通信特性
管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现。特质:
1.伪文件(文件占用磁盘空间,伪文件占用内存空间)
2.管道中的数据只能一次读取。
3.数据在管道中,只能单向流动。局限性:
1.自己写,不能自己读。
2.数据不可以反复读。
3.半双工通信。
4.血缘关系进程间可用。
十五、管道函数pipe
pipe函数:创建,并打开管道。
int pipe(int fd[2]);参数:
fd[0]:读端。
fd[1]:写端。返回值:
成功:0
失败:-1 errno
#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;
}
十六、管道读写行为
读管道:
1.管道中有数据,read返回实际读到的字节数。
2.管道中无数据:(1)管道写端被全部关闭,read返回0(类似读到文件结尾)。(2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据到达)写管道:
1.管道读端全部被关闭,进程异常终止(SIGPIPE信号导致的)
2.管道读端没有全部关闭:(1)管道已满,write阻塞等待。(2)管道未满,write将数据写入,并返回实际写入的字节数。
#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父进程写数据
#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
#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
#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;
}
十九、管道的一写端多读端
#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创建
#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实现非血缘关系进程通信
#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
#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命名管道参数
以上实现了一读端多写端如果是一写端多读端就会出现接收数据混乱(如下图)两者都需要多个bash窗口来实现,而且各个进程毫无血缘关系
二十二、mmap、munmap函数
存储映射I/O(Memory-mapped I/O)使一个磁盘文件与存储空间中的一个缓冲区相映射。
这个映射工作可以通过mmap函数来实现
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_WRITEflags:标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATEfd:用于创建共享内存映射区的那个文件的文件描述符。offset:默认0,表示映射文件全部。偏移位置。需是4K的整数倍。返回值:成功:映射区的首地址。失败:MAP_FAILED,errno
int munmap (void *addr, size_t length);
释放映射区。
addr:mmap的返回值
length:大小
二十三、mmap建立映射区
#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使用注意事项
使用注意事项:
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通信
#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值不同是因为变量读时共享,写时覆盖(相当于父子两份各自的数据,互不干涉)
#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通信
#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;
}//写端进程
#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;
}//读端代码
//由于先打开写端,已经写入几个数据,读的时候已经错过了前面的数据
无血缘关系进程间通信。
map:数据可以重复读取。
fifo:数据只能一次读取。例如:mmap写端进程sleep(2),读端进程sleep(1),
读端会读出两个重复的数据,因为1s写端还没有新的数据写入
二十七、mmap匿名映射区
#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文件不存在,通信时不需要额外创建一个通信文件
#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;
}
#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操作系统,因为它没有ANONYMOUSp = (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;
}
注意:以上所有匿名映射区只适用于父子进程,无血缘关系进程如兄弟进程不行,
因为父子进程共享fd文件描述符和mmap映射区