传入自定义系统调用的用户空间指针在调用内变更的原因排查
问题描述
在开发Linux内核系统调用时遇到以下问题:将用户空间指针作为参数传入系统调用后,用户态确认的指针值在内核态打印时发生了变化,但copy_to_user却成功完成了数据复制。
内核态系统调用代码
SYSCALL_DEFINE1(hello, char __user*, userland) { char *c = "test string \n"; printk("copying bytes to %p \n",(void*)userland); int n = copy_to_user((void *)userland,(void *)c,14); return 0; }
用户态测试代码
void *addr = 0xffffff000000; void *ptr = mmap(addr,256,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0); printf("address allocated by mmap %p \n",ptr); syscall(451,(char *)ptr); printf("contents of the string : %s \n",(char *)ptr);
程序输出
address allocated by mmap 0xffffff000000 copying bytes to 0x00000e8084e0 contents of the string : test string
从输出可见,系统调用内打印的地址与传入的地址不一致,但数据却成功复制,请问该地址差异的原因是什么?
原因分析
这个地址差异的核心是用户态虚拟地址和内核访问用户空间时用的线性地址不是同一个,但二者最终映射到同一块物理内存。
具体细节:
- 用户态里的
0xffffff000000是当前进程专属虚拟地址空间里的地址,每个进程都有独立的用户虚拟地址空间,这个地址只在当前用户进程的上下文里有效。 - 内核有自己独立的虚拟地址空间,当要访问用户空间内存时,会通过内核页表把用户虚拟地址转成内核能直接访问的线性地址——也就是你在
printk里看到的0x00000e8084e0。 copy_to_user函数内部会自动处理用户虚拟地址到内核可访问地址的转换逻辑,所以哪怕打印出来的地址看着不一样,实际操作的都是同一块物理内存,自然能成功完成数据复制。
内容的提问来源于stack exchange,提问作者Hrushikesh
相关产品推荐
相关产品推荐

