一、虚拟内存与物理映射
二、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映射区