遇到个linux编程问题请教一下各位大佬,在一个程序的主线程里面,使用shm_open创建了一个共享内存,然后mmap出来内存指针, 在主线程里面对这个共享内存进行赋值、读取都没问题,但是主线程创建的子线程中,直接对这个内存指针进行赋值时,就会segment fault。
因为代码在家里电脑上,主体含义就是下面的代码
#define MMAP_DATA_SIZE  1024
char *data = NULL;
//主线程
 int main(void)
{
        int fd = shm_open("shm0001", O_CREAT|O_RDWR, 0777);
 
        if (fd < 0) {
                printf("shm_open failed!\n");
                return -1;
        }
 
        ftruncate(fd, MMAP_DATA_SIZE);
        data = (char*)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        // 创建子线程
        .....
        while(1) {
            sleep(1);
        }
}
//子线程
 int subthread(void *arg) 
{
       if( data ) {
               memset(data, 0xff, 20); // 发生segment fault
       }
}我本来的理解是 这个共享内存,按理说在同一个进程的多线程内,内存空间应该是统一的,应该只要mmap一次吧?或者说是不是要在子线程里面再mmap一次
离线
测试发现即使在子线程中初始化和mmap,也会segment fault- -
离线
测试发现即使在子线程中初始化和mmap,也会segment fault- -
这么神奇诡异?
离线
结贴,是因为我写了这样一个函数在另一个C文件中,
void initshm(void)
{
	fd = shm_open("shm0001", O_CREAT|O_RDWR, 0777);
	if (fd < 0) {
			printf("shm_open failed!\n");
			return ;
	}
	ftruncate(fd, MMAP_DATA_SIZE);
	data = (char*)mmap(NULL, MMAP_DATA_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}
char *getpshm(void)
{
	return pshm;
}然而在main.c中,没有声明char *getpshm(void); 一般来说,没有声明的函数好像默认的返回类型是int,不知为啥直接使用就出错了。  
之前编译的时候就看到有这个警告,不过我选择了视而不见- -
离线

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#define MMAP_DATA_SIZE  1024
char *data = NULL;
pthread_t pid_sub;
//子线程
void *subthread(void *arg) 
{
   if( data ) {
       memset(data, 0xff, 20); // 发生segment fault
       printf("subthread memset.\n");
   }
}
//主线程
 int main(void)
{
    int rc=0;
    int fd = shm_open("shm0001", O_CREAT|O_RDWR, 0777);
    if (fd < 0) {
            printf("shm_open failed!\n");
            return -1;
    }
    ftruncate(fd, MMAP_DATA_SIZE);
    data = (char*)mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // 创建子线程
    rc = pthread_create(&pid_sub, NULL, subthread, NULL);
    while(1) {
        sleep(1);
    }
}compile: gcc test.c -lrt -lpthread -o test
测试没问题
离线
@阿黄 还是没看懂是什么原因引起的?
离线
test2.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <fcntl.h>
#include <string.h>
#define MMAP_DATA_SIZE  1024
char *data = NULL;
int fd ;
char *getpshm(void)
{
	return data;
}
void initshm(void)
{
	fd = shm_open("shm0001", O_CREAT|O_RDWR, 0777);
	if (fd < 0) {
			printf("shm_open failed!\n");
			return ;
	}
	ftruncate(fd, MMAP_DATA_SIZE);
	data = (char*)mmap(NULL, MMAP_DATA_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
}main.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <fcntl.h>
#include <string.h>
//char *getpshm(void);
void initshm(void);
//子线程
void *subthread(void *arg) 
{
	char *p = getpshm();
	printf("subthread started!\n");
	if( p ) {
		   memset(p, 0xff, 20); // 发生segment fault
		   printf("shm test success!\n");
	}
	while(1) {
		sleep(1);
	}
}
//主线程
 int main(void)
{
	int err;
	pthread_t tid;
	
	initshm();
	// 创建子线程
	err = pthread_create(&tid, NULL, subthread, NULL);
	if( err ) {
		perror("create eth poll thread");
		exit(EXIT_FAILURE);
	}
	while(1) {
		sleep(1);
	}
}main.c中没有声明test2.c中的 char *getpshm(void) 函数,出了一个警告。
warning: initialization makes pointer from integer without a cast [-Wint-conversion]这时候运行到memset的时候 就会出现segment fault
最近编辑记录 阿黄 (2021-04-22 23:05:16)
离线
很感兴趣的问题,拿着楼主的代码仔细的走了走发现这个问题其实还是编译器对未声明的函数默认状态处理的问题。
当函数不存在声明的时候,应该默认为int func(void)作为函数的状态的,这个可以通过调用约定和反汇编看到,只存在rax返回值,且默认还清零了。
故gcc将这个64位地址截断为32位的int值再拓展到了64位上去了,这才是后面段错误的核心原因。
看来这种不明显的警告很多时候还是非常危险,应该多确认下是否有异常。
1. 通过gdb进入到getpshm函数内,打印data地址信息,发现该地址是个正确地址
(gdb) p data
$3 = 0x7ffff7ffb000 '\377' <repeats 20 times>
2. 手动将程序执行到程序的末尾位置,打印返回值寄存器信息。此时返回值是一个正常的状态
(gdb) info registers eax
eax            0xf7ffb000          -134238208
(gdb) info registers rax
rax            0x7ffff7ffb000      140737354117120
3. 继续程序执行,完成函数,打印返回值寄存器信息,此时异常已经发生。
(gdb) info registers rax
rax            0xfffffffff7ffb000  -134238208
(gdb) info registers eax
eax            0xf7ffb000          -134238208
4. 继续执行到memset时,该地址空间处于不可访问空间地址,写操作必然崩溃而引起segment fault。
(gdb) disassemble 
Dump of assembler code for function subthread:
   0x0000555555555249 <+0>:	endbr64 
   0x000055555555524d <+4>:	push   %rbp
   0x000055555555524e <+5>:	mov    %rsp,%rbp
   0x0000555555555251 <+8>:	sub    $0x20,%rsp
   0x0000555555555255 <+12>:	mov    %rdi,-0x18(%rbp)
   0x0000555555555259 <+16>:	mov    $0x0,%eax
   0x000055555555525e <+21>:	callq  0x555555555312 <getpshm>
=> 0x0000555555555263 <+26>:	cltq   //cltq指令,特指%eax->%rax的符号拓展转换,等价于movslq %eax,%rax
   0x0000555555555265 <+28>:	mov    %rax,-0x8(%rbp)
   0x0000555555555269 <+32>:	lea    0xd94(%rip),%rdi        # 0x555555556004
   0x0000555555555270 <+39>:	callq  0x5555555550e0 <puts@plt>
   0x0000555555555275 <+44>:	cmpq   $0x0,-0x8(%rbp)
   0x000055555555527a <+49>:	je     0x55555555529e <subthread+85>
   0x000055555555527c <+51>:	mov    -0x8(%rbp),%rax
   0x0000555555555280 <+55>:	mov    $0x14,%edx
   0x0000555555555285 <+60>:	mov    $0xff,%esi
   0x000055555555528a <+65>:	mov    %rax,%rdi
   0x000055555555528d <+68>:	callq  0x555555555120 <memset@plt>
   0x0000555555555292 <+73>:	lea    0xd7e(%rip),%rdi        # 0x555555556017
   0x0000555555555299 <+80>:	callq  0x5555555550e0 <puts@plt>
   0x000055555555529e <+85>:	mov    $0x1,%edi
   0x00005555555552a3 <+90>:	callq  0x555555555150 <sleep@plt>
   0x00005555555552a8 <+95>:	jmp    0x55555555529e <subthread+85>
End of assembler dump.离线
顺带一提的是,当编译选择位32位的时候是没有问题。
gcc main.c test.c -m32 -lrt -lpthread
更加印证了上述的逻辑。
离线
很感兴趣的问题,拿着楼主的代码仔细的走了走发现这个问题其实还是编译器对未声明的函数默认状态处理的问题。
当函数不存在声明的时候,应该默认为int func(void)作为函数的状态的,这个可以通过调用约定和反汇编看到,只存在rax返回值,且默认还清零了。
故gcc将这个64位地址截断为32位的int值再拓展到了64位上去了,这才是后面段错误的核心原因。
看来这种不明显的警告很多时候还是非常危险,应该多确认下是否有异常。1. 通过gdb进入到getpshm函数内,打印data地址信息,发现该地址是个正确地址
(gdb) p data
$3 = 0x7ffff7ffb000 '\377' <repeats 20 times>
2. 手动将程序执行到程序的末尾位置,打印返回值寄存器信息。此时返回值是一个正常的状态
(gdb) info registers eax
eax 0xf7ffb000 -134238208
(gdb) info registers rax
rax 0x7ffff7ffb000 140737354117120
3. 继续程序执行,完成函数,打印返回值寄存器信息,此时异常已经发生。
(gdb) info registers rax
rax 0xfffffffff7ffb000 -134238208
(gdb) info registers eax
eax 0xf7ffb000 -134238208
4. 继续执行到memset时,该地址空间处于不可访问空间地址,写操作必然崩溃而引起segment fault。(gdb) disassemble Dump of assembler code for function subthread: 0x0000555555555249 <+0>: endbr64 0x000055555555524d <+4>: push %rbp 0x000055555555524e <+5>: mov %rsp,%rbp 0x0000555555555251 <+8>: sub $0x20,%rsp 0x0000555555555255 <+12>: mov %rdi,-0x18(%rbp) 0x0000555555555259 <+16>: mov $0x0,%eax 0x000055555555525e <+21>: callq 0x555555555312 <getpshm> => 0x0000555555555263 <+26>: cltq //cltq指令,特指%eax->%rax的符号拓展转换,等价于movslq %eax,%rax 0x0000555555555265 <+28>: mov %rax,-0x8(%rbp) 0x0000555555555269 <+32>: lea 0xd94(%rip),%rdi # 0x555555556004 0x0000555555555270 <+39>: callq 0x5555555550e0 <puts@plt> 0x0000555555555275 <+44>: cmpq $0x0,-0x8(%rbp) 0x000055555555527a <+49>: je 0x55555555529e <subthread+85> 0x000055555555527c <+51>: mov -0x8(%rbp),%rax 0x0000555555555280 <+55>: mov $0x14,%edx 0x0000555555555285 <+60>: mov $0xff,%esi 0x000055555555528a <+65>: mov %rax,%rdi 0x000055555555528d <+68>: callq 0x555555555120 <memset@plt> 0x0000555555555292 <+73>: lea 0xd7e(%rip),%rdi # 0x555555556017 0x0000555555555299 <+80>: callq 0x5555555550e0 <puts@plt> 0x000055555555529e <+85>: mov $0x1,%edi 0x00005555555552a3 <+90>: callq 0x555555555150 <sleep@plt> 0x00005555555552a8 <+95>: jmp 0x55555555529e <subthread+85> End of assembler dump.
拜读了大佬的帖子,豁然开朗 - - 谢谢
离线