学 网站开发,太原网页设计,自己网站建设多少钱,广告制作流程步骤8.内存映射
8.1 内存映射相关定义
创建一个文件#xff0c;将保存在磁盘中的文件映射到内存中#xff0c;后期两个进程之间对内存中的数据进行操作#xff0c;大大减少了访问磁盘的时间#xff0c;也是一种最快的 IPC #xff0c;因为进程之间可以直接对内存进行存取 8.…8.内存映射
8.1 内存映射相关定义
创建一个文件将保存在磁盘中的文件映射到内存中后期两个进程之间对内存中的数据进行操作大大减少了访问磁盘的时间也是一种最快的 IPC 因为进程之间可以直接对内存进行存取 8.2 内存映射相关的系统调用
代码地址lesson25
8.2.1 mmap munmap 函数–内存的映射与释放
1.API mmap : 将一个文件映射到内存中
这个函数返回的是内存映射出来的内存首地址
munmap 解除一个文件和内存的映射
内存映射和管道不一样内存映射不会发生阻塞
2.代码
通过内存映射的方法实现两个关系进程之间的通信
①导入相应的库文件
#include sys/mman.h
#include fcntl.h
#include sys/types.h
#include unistd.h
#include string.h
#include stdlib.h
#include wait.h②将一个文件映射到内存区
int fd open(test.txt,O_RDWR);
int size lseek(fd,0,SEEK_END); // 获取文件大小从index0 到 index_end
// 创建映射
void* ptr mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0); // prot 内存映射区域的权限一般读写都有flag:本地文件是否和盘文件同步
if(ptrMAP_FAILED){perror(mmap);exit(0);
}③父进程等待子进程写入数据并读出来子进程将数据写入到内存中
// 创建子进程
pid_t pid fork();
if(pid0){ // 父进程读取文件wait(NULL); // 等待子进程写完内存映射区后再去读文件char buf[64];strcpy(buf,(char*)ptr); //拷贝字符串printf(read data:%s\n,buf);
}else if(pid0){// 子进程向映射区写入数据strcpy((char*)ptr,xuboluo);}④关闭映射
munmap(ptr,size);3.代码演示
最后可以看到这里在对内存映射时文件中的文字是进行覆盖的 8.2.2 mmap munmap 内存的映射与释放 中常见问题 可以对其 操作更改内存映射的开始地址这样在写文件时就可以从指定的位置开始写
但是在进行释放内存时要传入正确的释放地址也就是一开始系统给分配的地址 映射的权限要小于等于文件 open 时的权限
3. 文件偏移量的作用就是指明从文件的哪个地方开始映射如下图 一般页面大小为 4K 虚拟内存空间的大小也为 4K 映射的文件大小必须要 0 这样在分配虚拟内存时才能保证能分配到空间。可以使用相应的方法扩展内存 创建映射区时会对文件描述符进行一个拷贝即使文件被关闭也不会有影响 8.2.3 使用内存映射实现文件拷贝
代码保存在 lesson25 copy.c 中
1.基本思路 现在硬盘中有两个文件 A 和 B A 文件和 B 文件分别映射到内存当中并获得相应的 map_ptr ,两个 ptr 之间进行通讯 2.代码实现
#include string.h
#include stdlib.hint main(){// 1.得到两个文件的文件描述符int fd_a open(a.txt,O_RDWR);int fd_b open(b.txt,O_RDWR | O_CREAT,0664);// 2.得到文件大小int len lseek(fd_a,0,SEEK_END);// 对新建的文件进行扩展truncate(b.txt,len);write(fd_b, ,1);// 3.得到文件映射int* ptr_a mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd_a,0);int* ptr_b mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd_b,0);//4.内存拷贝memcpy(ptr_b,ptr_a,len); // 目标文件源文件长度// 5.释放资源munmap(ptr_a,len);munmap(ptr_b,len);// 6.关闭两个文件流close(fd_a);close(fd_b);return 0;
}3.代码演示
b.txt 文件原本是不存在的空文件拷贝后有了 a.txt 的内容 8.2.4 文件的匿名映射
代码保存在 lesson25 mmap-anon.c
有时候不存在真正的文件实体不需要文件这个中介进行通信。比如说进程将读到的内容直接传递给子进程
文件的匿名映射只能存在于有关系的两个进程之间
1.代码实现
创建一个父进程将读到的 string 中的信息直接传递给子进程。关键代码是需要在 mmap 映射时添加匿名映射的属性
MAP_ANONYMOUS#include stdio.h
#include sys/mman.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
#include stdlib.h
#include sys/wait.h
int main(){// 1.定义内存映射区的大小。以往都是根据文件大小来int len 4096;void* ptr mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);// 2.父子间进程通信pid_t pid fork();if(pid0){// 父进程向内存缓冲区写入内容strcpy((char*)ptr,hello world);wait(NULL);}else if(pid 0){// 子进程读取内存映射的内容sleep(1); // 先等父进程写完printf(%s\n,(char*)ptr);}//3.释放映射int ret munmap(ptr,len);return 0;
}2.代码演示
最后可以看到虽然没有创建文件但还是实现了两个进程之间的通信 9.信号
9.1 信号的基本概念
1.定义
软件中断就是指当有另一个进程到达时此进程暂停当前任务去执行另一个进程的任务。等待另一个进程执行完毕再返回到刚才的任务继续执行是一种异步通信方式。
信号 软件中断 2.中断发生的场景
对于前台进程用户输入了特殊的符号比如 Ctrlc ,这时就向进程发了一个中断信号硬件发生异常硬件检测到一个错误条件并通知内核随即硬核再发送相关信号通知进程。比如执行了一条异常的机器指令或者被 0 除或者访问了一块无法访问的内存区域系统状态的变化定时器到期引起 SIGALRM 信号进程执行的 CPU 时间超限或者该进程的某个子进程退出执行 kill 命令或者调用 kill 函数
信号主要是软中断告诉另一个进程可以执行了所以说信号软中断也是一种进程通讯的方式
3.信号特点
简单不能携带大量信息满足某个特定条件才发送优先级高
9.2 信号介绍
使用 kill -l 就可以看到所有的信号
前 31 个为常规信号其余为实时信号 只看红色部分 -9 SIGKILL
SIGCHLD子进程结束时父进程会收到这个信号
下图中的 Core 文件是指我们在终止进程时对于中止进程保存的相关信息 man 7 signal // 查看信号指令可以发现有的信号用三个数字表示这是因为不同的系统架构造成的我们在使用这些信号时选择中间的数字即可
SIGKILL and SIGSTOP 不能被捕捉不能被阻塞也不能被忽略 9.2.1 Core 文件保存中断中间变量
2.代码
这里定义一个会报错的代码定义一个 string ,但是不将这个 string 进行初始化直接对其赋值那么最终也会导致一个 “段错误” 的出现 3.代码演示
这里会爆出一个段错误主要是因为没有进行一个争取的段地址映射而产生的 4.添加 Core 文件
首先使用下面的指令查看和更改 Core 文件的大小,并且看到系统分配的 core 文件大小为 0
ulimit -a使用下面代码更改生层的 Core 文件大小
ulimit -c 1024 // 定义 Core 文件的大小为 1024因为我的代码没有显示对应的效果所以这里用了视频中的演示 我们通过 gdb 的方式对 core 文件进行查看里面说他是在执行 a.out 文件时产生的执行了进程的终止并且是因为段错误发送了 SIGSEGV 信号 9.3 发送信号的相关函数 9.3.1 Kill–给指定进程发送信号
1.API 2.代码
实现 kill -9 操作
关键代码
kill(pid, SIGINT);#includestdio.h
#includesys/types.h
#includesignal.h
#includeunistd.hint main(){pid_t pid fork();if(pid 0) {// 子进程int i 0;for(i 0; i 5; i) {printf(child process\n);sleep(1);}} else if(pid 0) {// 父进程printf(parent process\n);sleep(2);printf(kill child process now\n);kill(pid, SIGINT);}return 0;
}3.代码演示 9.3.2 raise --给当前进程发送信号
1.API
在 man 3 raise 中查看相关文档
这个函数可以由 kill 函数实现 9.3.3 abort–杀死当前进程
1.API
这个函数可以由 kill 函数实现 9.3.4 alarm 定时器–在一定的时长之后发送信号
1.API
定时器只能设置一次后面再设置定时器还是按照第一次的时间进行倒计时
而且不管进程是什么状态 alarm 都会执行 2.代码
设置一个定时器
#includestdio.h
#includeunistd.h
int main(){int seconds alarm(5); // 设置一个 5 s 是定时器sleep(2);seconds alarm(1); // 这个闹钟无效printf(second%d\n,seconds); // 3while(1){}return 0;
}3.代码演示
虽然在 5 秒的闹钟后加闹钟加快但是最后得到的闹钟还是 3 秒的剩余时间也就是按照第一次闹钟定时 4.计数器代码实现
计数器实现电脑 1s 可以数多少个数
#includestdio.h
#includeunistd.hint main(){alarm(1); // 1s 之后杀死自己int i 0;while(1){printf(%i\n,i);}return 0;
}5.程序的运行时间
程序真正的运行时间 内核时间用户时间程序真正的运行时间消耗的时间比如文件 IO 操作打开关闭消耗的时间
9.3.5 setitimer–实现周期性定时
1.API
这个定时器可以根据周期设置在多少时间之后启动每隔多少秒启动一次 2.代码
关键代码:
如何定义结构体 new_value
int ret setitimer(ITIMER_REAL,new_value,NULL);#includesys/time.h
#includestdlib.h
#includestdio.h
int main(){// 定义结构体属性参数struct itimerval new_value;// 过3秒后每隔2秒进行一次定时new_value.it_interval.tv_sec2; // 每隔 2s 唤醒一次定时器new_value.it_interval.tv_usec 0;new_value.it_value.tv_sec 3; // 3 秒之后在设置这个定时器new_value.it_value.tv_usec 0;// 设置一个周期定时器int ret setitimer(ITIMER_REAL,new_value,NULL);printf(定时器开始了);if(ret-1){perror(setitimer);exit(0);}getchar(); // 防止进程提前终止return 0;
}3.代码实现
首先是先打印定时器开始了
在 3s 之后打印 Alarm clock 9.4信号捕捉函数
9.4.1 signal 捕捉信号
1.API
在定义信号之前定义捕捉信号的函数
捕捉到信号然后执行自己想执行的事情 在形参中需要传入一个 sighandler 通过前面定义发现这是一个回调函数 2.代码
signal(SIGALRM,SIG_IGN); // 忽略信号3.代码演示
忽略信号
刚才使用 setitimer 定义了一个信号这里使用 signal 对信号忽略可以看到它就一直在要求用户输入的地方不会终止程序 默认信号执行
时隔3s后执行 Alarm clock 执行回调函数
在 3s 之后 signal 捕捉到第一个信号然后每隔 2s 就会捕捉到另一个信号捕捉到信号后执行相应的 handler 函数。执行完 handler 函数后程序继续执行下一个信号的发送并没有终止程序 9.4.2 信号集的捕捉
1.API
1什么是信号集
PCB 中有两个信号集阻塞信号集和未决信号集。我们可以借助信号集操作函数对这两个信号集进行操作
阻塞信号集
将某些信号加⼊集合对他们设置屏蔽当屏蔽x信号后再收到该信号该信号的处理将推后(处理发⽣在解除屏蔽后)
未决信号集
这个信号还没有被处理。信号产⽣未决信号集中描述该信号的位⽴刻翻转为1表示信号处于未决状态。当信号被处理对应位翻转回为0。这⼀时刻往往⾮常短暂。
2如何处理信号
根据信号集中的值处理信号1 代表这个信号被阻塞不能执行未决0 代表信号可以抵达未决信号集中的值根据阻塞信号集中信号的变化而变化
e.g. : 当前的处理的信号 index 为 2 也就是 SIGINT 信号
阻塞信号集中的值为 0 未决信号集看到阻塞信号集他的值为 0 也就是说不阻塞则未决信号集中的值也变成 0
阻塞信号集中的值为 1 未决信号集看到阻塞信号集他的值为 1 先暂时将这个信号进行挂起直到信号不阻塞了未决信号的值再变为0
如果有更多的阻塞信号到达只能被丢弃 信号集
3操控信号集的常见函数 #include signal.h
int sigemptyset(sigset_t *set); // 将set集合置空
int sigfillset(sigset_t *set); // 将所有信号加⼊set集合
int sigaddset(sigset_t *set, int signo); // 将signo信号加⼊到set集合
int sigdelset(sigset_t *set, int signo); // 从set集合中移除signo信号
int sigismember(const sigset_t *set, int signo); // 判断信号是否存在信号集的数据结构
sigset_t2.代码
#include sys/time.h
#include stdio.h
#include stdlib.h
#include signal.hint main(){// 1.定义一个信号集sigset_t set;// 2.清空信号集中的内容sigemptyset(set);// 3. 判断信号是否还在信号集 set 中,即它是否被阻塞int ret sigismember(set,SIGINT);if(ret1){printf(设置阻塞成功\n);}else if(ret0){printf(设置阻塞失败\n);}// 4. 添加几个信号到信号集中sigaddset(set,SIGINT);sigaddset(set,SIGQUIT);// 5.判断添加的信号是否在信号集中ret sigismember(set,SIGINT);if(ret1){printf(被阻塞了\n);}else if(ret0){printf(没有被阻塞\n);}return 0;
}3.代码演示
根据上面的代码可以看出我们先对信号集中的信号进行清空然后查看信号集中的成员。然后再设置信号再次查看信号集中的成员 9.4.3 sigaction—信号捕捉函数
代码保存在 lesson26 sigaction.c 中
1.API
类似于 signal 函数捕捉信号
#include signal.h
/**
* 检查或修改指定信号的设置或同时执⾏这两种操作.
* param signum 要操作的信号.
* param act 要设置的对信号的处理⽅式传⼊参数.
* param oldact一般不用原来对信号的处理⽅式传出参数 . *
* 如果 act 指针⾮空则要改变指定信号的处理⽅式设置
* 如果 oldact 指针⾮空则系统将此前指定信号的处理⽅式存⼊ oldact.
*
* return 成功: 0; 失败: -1.
*/
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);数据结构
struct sigaction {
void(*sa_handler)(int); // 信号处理函数指针
void(*sa_sigaction)(int, siginfo_t *, void *); // 一般不用新的信号处理函数指针
sigset_t sa_mask; // 临时信号阻塞集在信号捕捉函数执行过程中临时阻塞某些信号
int sa_flags; // 使用哪个信号处理函数处理信号0sa_handler,SA_SIGINFO sa_sigaction
void(*sa_restorer)(void); //已弃⽤
};sigaction 讲解
2.代码
首先设置一个定时器不断的发送信号然后使用 sigaction 捕捉信号捕捉到信号后执行相应的操作操作结束后程序继续执行
关键代码
定义信号捕捉函数重点在于如何定义结构体变量
// 定义 act
struct sigaction act;
act.sa_flags 0;// 使用 handelr 进行处理
act.sa_handler my_alarm;
sigemptyset(act.sa_mask); // 清空临时信号集
// 注册信号捕捉函数
sigaction(SIGALRM,act,NULL);捕捉到信号后进行的操作
// 捕捉信号后处理的函数
void my_alarm(int num){printf(捕捉到一个信号%d\n,num);printf(进行相应的处理\n);}3.代码演示 9.4.5 信号捕捉流程及特性
1.流程 1程序在 main 函数中执行运行到某一行时发现需要中断然后程序就运行到内核
2内核使用 do_signal 函数处理递送的信号
3内核判断信号处理函数是否是用户自定义的如果是用户自定义的函数则再返回到用户区执行相应的信号处理函数
4信号处理函数处理完成后调用 sigreturn 返回到内核
5从内核再返回用户区继续刚才没有执行完的 main 程序执行
2.特性
1内核中有一个阻塞信号集信号捕捉时也有一个临时信号集当信号捕捉结束后会返回到系统中的阻塞信号集
2当两个信号同时发生后面的阻塞信号要等前面的阻塞信号处理完毕才能接着处理
3阻塞的常规信号是不支持排队的因为信号集的个数只能由 01表示其他来的阻塞信号要全部被丢弃
9.4.6 SIGCHLD 信号
代码保存在 lesson26 sigchld.c 中
1.子进程什么时候会向父进程发送 SIGCHLD 信号
子进程会向父进程发送 SIGCHLD 信号以下三种情况发生时子进程就会想父进程发送 SIGCHLD 信号默认情况下父进程会忽略该信号 1.子进程终止时
2.代码
首先注册阻塞信号集并将其添加到系统阻塞信号集中。其次开启子进程子进程执行相应方法。当子进程执行完毕后父进程对子进程进行回收回收完毕接着执行自己的代码
调用相应的库文件
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include signal.h
#include sys/wait.h注册信号阻塞集将其添加到系统信号阻塞集中
// 提前设置好阻塞信号集阻塞 SIGCHLD 有可能因为子进程结束父进程还没有注册阻塞信号集就没有办法阻塞子进程
sigset_t set; // 定义信号集
sigemptyset(set); // 将信号集中的信号清空
sigaddset(set,SIGCHLD); // 添加对子进程阻塞的监听
sigprocmask(SIG_BLOCK,set,NULL); // 将定义的阻塞信号集添加到系统的阻塞信号集中信号处理函数即回收子进程
void my_handler(int num){printf(捕捉到的信号%d\n,num);// 回收子进程 pcb 资源,防止有漏掉的子进程while(1){int ret waitpid(-1,NULL,WNOHANG); // 进程回收if(ret0){printf(child die ,pid%d\n,ret);}else if(ret0){ // 还有子进程没有完全回收break;}else if(ret-1){ // 完全回收break;}}
}创建子进程
// 创建子进程
pid_t pid;
for(int i0;i5;i){pid fork();if(pid0) break;
}父进程不断执行并不断监听子进程发出的 SIGCHLD 信号
if(pid0){// 捕捉子进程死亡时 SIGCHLD 信号struct sigaction act;act.sa_flags 0;act.sa_handler my_handler;sigemptyset(act.sa_mask); // 将临时阻塞进程集设置为空sigaction(SIGCHLD,act,NULL);// 注册完信号捕捉后解除阻塞sigprocmask(SIG_UNBLOCK,set,NULL);while(1){ // 父进程保持执行状态printf(parent process pid:%d\n,getpid());sleep(2);}}子进程的相关函数
else if(pid0){printf(child process pid:%d\n,getpid());}3.代码演示