OS54.【Linux】System V 共享内存(3) “共享内存+管道“修bug记录

🧨🧨🧨🧨🧨🧨🧨🧨大年初一篇🧨🧨🧨🧨🧨🧨🧨🧨

🎉❤️🎉❤️🎉❤️🎉❤️🎉❤️祝各位程序员们马年大吉 万事如意!❤️🎉❤️🎉❤️🎉❤️🎉❤️🎉

目录

1.情景回顾

2.排查

事故现场

[开启core dump](#开启core dump)

使用gdb进行事后调试

3.修改


1.情景回顾

我在OS53.【Linux】System V 共享内存(2)文章写了一个"共享内存+管道"的实现代码,运行结果没问题,但是我写的最初版本是有问题的,如下:

header.hpp写入:

cpp 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define PROJ_ID 0x1344 
key_t get_key()
{
    char buffer[SIZE];
    char* pathname=getcwd(buffer,sizeof(buffer));
    if (pathname==nullptr)
    {
        perror("getcwd failed");
        exit(1);
    }
    return ftok(pathname,PROJ_ID);
}

//使用IPC_EXCL来确保获得新的共享内存
int get_new_shared_memory()
{
    key_t key=get_key();
    //申请新的共享内存
    int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL);
    return shmid;
}

int get_old_shared_memory()
{
    key_t key=get_key();
    //获取旧的共享内存
    int shmid=shmget(key,4097,IPC_CREAT|0666);
    return shmid;
}

shm1.cpp写入:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "header.hpp"
int main()  
{ 
    //为了减小代码行数,系统调用的错误执行结果都没有判断
    mkfifo("./tmp_fifo",0664);
    int fd=open("./tmp_fifo",O_RDONLY);
    int shmid=get_new_shared_memory();
    char* start_addr=(char*)shmat(shmid,nullptr,0);
    for (;;) 
    {
        char ch;
        ssize_t n=read(fd,&ch,1);
        if (n==0||n<0)
            break;
        std::cout<<start_addr<<std::endl;
    }
    shmdt(start_addr);
    shmctl(shmid,IPC_RMID,nullptr);
    unlink("./tmp_fifo");
    return 0;
}

shm2.cpp写入:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "header.hpp"  
int main()  
{
    //为了减小代码行数,系统调用的错误执行结果都没有判断  
    int fd=open("./tmp_fifo",O_WRONLY);
    int shmid=get_old_shared_memory(); 
    char* start_addr=(char*)shmat(shmid,nullptr,0);
    for (;;)
    {
        std::cin>>start_addr;
        write(fd,"",1);
    }
    shmdt(start_addr);
    return 0;
}

★提示: 此bug非常隐蔽,不仔细分析很难查出问题,非常锻炼调试能力!

2.排查

事故现场

未运行shm1.out和shm2.out时,操作系统中没有任何共享内存:

编译以上代码,然后先运行shm1.out,再运行shm2.out:

shm1.out崩溃后,Ctrl+C退出shm2.out后,查看共享内存:

开启core dump

发现报了"Segmentation fault",为了能进一步查明问题,可以先启用Linux的core dump功能:

cpp 复制代码
ulimit -c unlimited #让core dump文件大小无限制

在有些云服务器上,虽然设置了core dump文件大小无限制,但是仍然没有在崩溃进程运行的目录下生成core dump文件,使用以下命令:

cpp 复制代码
sudo bash -c 'echo "core.%p" > /proc/sys/kernel/core_pattern'

注: 该命令是临时修改的,重启会失效

这样让操作系统生成核心转储core dump然后开启gdb进行事后调试,能较为容易定位问题

打开core dump功能后,生成了core.40405文件:

使用gdb进行事后调试

从上面的输出的结果来看,shm1.out先运行,其打开tmp_fifo管道的读端,由于写端未打开,故shm1.out阻塞,再运行shm2.out,其打开tmp_fifo管道的写端,接着shm2.out向共享内存中写入用户的输入,为什么shm1.out会段错误呢?

使用gdb进行事后调试:

给的报错有点奇怪,没太明白:

cpp 复制代码
Program terminated with signal SIGSEGV, Segmentation fault.
#0  __strlen_evex () at ../sysdeps/x86_64/multiarch/strlen-evex.S:450

看看堆栈信息:

bash 复制代码
bt

没看出什么问题:

再看看堆栈详细信息:

bash 复制代码
bt full

注意到:

++start_addr = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>++

虚拟地址为0xffffffffffffffff,为64位虚拟地址的最高端,说明shmat的返回值有问题,man手册查看:

shmat要么返回有效地址,要么返回-1,-1在64位下表示为0xffffffffffffffff ,说明shmat未能成功挂接共享内存

再仔细看看函数的调用顺序:

按照栈先进后出的原则,调用顺序为:

main()-->cout-->__strlen_evex()

shm1.cpp中使用了std::cout<<start_addr<<std::endl,为std::cout<<(char*)0xffffffffffffffff<<std::endl肯定不行,所以报"Segmentation fault"

由于start_addr为0xffffffffffffffff,那么shmid肯定为-1,那么int shmid=get_new_shared_memory()中shmid得到-1,++反推原因: get_old_shared_memory()先执行,导致get_new_shared_memory()无法分配新的内存(因为加了IPC_EXCL标志位),所以shm1.out的shmid为-1!!!++

为了验证我的想法,改动shm2.cpp:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "header.hpp"  
int main()   
{
    //为了减小代码行数,系统调用的错误执行结果都没有判断  
    int fd=open("./tmp_fifo",O_WRONLY); 
    int shmid=get_old_shared_memory(); 
    char* start_addr=(char*)shmat(shmid,nullptr,0);
    printf("start_addr=0x%p",start_addr);
    for (;;)
    {
        std::cin>>start_addr;
        write(fd,"",1);
    }
    shmdt(start_addr);
    return 0;
}

运行结果:确实是我上述所说的那样

★★★错误关键点: shm1.out先运行,读端打开,但阻塞在open系统调用,下面的get_new_shared_memory()没有机会执行

bash 复制代码
int fd=open("./tmp_fifo",O_RDONLY);// <--阻塞在此处
//暂时无法运行下面的函数
int shmid=get_new_shared_memory();
char* start_addr=(char*)shmat(shmid,nullptr,0);

启动shm2.out后,写端打开,open系统调用执行完后,shm2.out继续执行,get_old_shared_memory()返回有效的地址

cpp 复制代码
int fd=open("./tmp_fifo",O_WRONLY); //<--接着写端打开后,管道的读写端都打开,不阻塞
//正常执行以下内容
int shmid=get_old_shared_memory(); 
char* start_addr=(char*)shmat(shmid,nullptr,0);
for (;;)
{
    std::cin>>start_addr;
    write(fd,"",1);
}

cin执行结束后,向管道写入数据

shm1.out执行get_new_shared_memory(),但发现共享内存已经分配过了,而且使用了IPC_EXCL,那么get_new_shared_memory()返回-1,即上方的0xffffffffffffffff,

cpp 复制代码
int get_new_shared_memory()
{
    key_t key=get_key();
    //申请新的共享内存
    int shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL);
    return shmid;
}

管道的读写端都打开时,管道中又有数据,read不阻塞,执行到std::cout<<start_addr<<std::endl;引发错误

cpp 复制代码
int shmid=get_new_shared_memory();
char* start_addr=(char*)shmat(shmid,nullptr,0);
for (;;) 
{
    char ch;
    ssize_t n=read(fd,&ch,1);
    if (n==0||n<0)
        break;
    std::cout<<start_addr<<std::endl;
}

3.修改

让get_new_shared_memory()先于get_old_shared_memory()执行即可

shm1.cpp写入:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include "header.hpp"
int main() 
{
    //为了减小代码行数,系统调用的错误执行结果都没有判断
    int shmid=get_new_shared_memory(); 
    char* start_addr=(char*)shmat(shmid,nullptr,0);
    for (;;) 
    {
        sleep(1);
        std::cout<<start_addr;
        fflush(stdout);
    }
    shmdt(start_addr);
    shmctl(shmid,IPC_RMID,nullptr);
    return 0;
}

shm2.cpp写入:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include "header.hpp"
int main() 
{
    //为了减小代码行数,系统调用的错误执行结果都没有判断  
    int shmid=get_old_shared_memory(); 
    char* start_addr=(char*)shmat(shmid,nullptr,0);
    for (;;)
    {
        std::cin>>start_addr;
    }
    shmdt(start_addr);
    return 0;
}

运行结果:

相关推荐
小付同学呀1 小时前
C语言学习(二)——C语言数据类型
数据结构·算法
之歆1 小时前
Varnish HTTP 缓存服务器完全指南
服务器·http·缓存
流云鹤1 小时前
牛客周赛Round 131
算法
重生之后端学习1 小时前
124. 二叉树中的最大路径和
java·数据结构·算法·职场和发展·深度优先·图论
mit6.8241 小时前
状压+dijk |floyd
算法
Renhao-Wan1 小时前
Java 算法实践(五):二叉树遍历与常见算法题
java·数据结构·算法
迎仔1 小时前
10-算力中心运维三剑客:Ansible + Jenkins + K8s 高效实战
运维·kubernetes·ansible·jenkins
一条大祥脚1 小时前
Z函数/拓展KMP
算法
追随者永远是胜利者2 小时前
(LeetCode-Hot100)39. 组合总和
java·算法·leetcode·职场和发展·go