之前说过内核态
和用户态
的事情,一个系统中所有的一切都依托于资源,而对于资源访问的控制衍生出特权
的概念,而用户态
和内核态
的区别就在于:
用户态
的进程可以访问的资源有限制内核态
对于资源的访问没有限制
然而一个程序的运行并非就单纯的限制在某个特权等级下,因此就必然涉及到特权切换
的过程。
在设计上,用户空间和内核空间的内存是不能够直接互访的,因为linux使用的虚拟内存机制,用户空间的数据可能会被换出,当内核空间使用用户空间指针时,数据可能已经不在内存了。
但其实并非没有办法,熟悉驱动开发的自然会知道copy_to_user
和copy_from_user
这两个函数。这两个函数可以完成从一个空间到另一个空间的复制,但是呢消耗也是明显的,看一下源码:
copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(check_copy_size(from, n, true)))
n = _copy_to_user(to, from, n);
return n;
}
这是4.15
的内核源码,很明显的在函数中有一个check_copy_size
的检查项,然而更久远之前的内核源码中,实际上是有一个access_ok
的指针检查的:
static inline long copy_to_user(void __user *to,
const void *from, unsigned long n)
{
might_fault();
if (access_ok(VERIFY_WRITE, to, n))
return __copy_to_user(to, from, n);
else
return n;
}
这儿直接套用别人的解答:
copy_to_user在每次拷贝时需要检测指针的合法性,也就是用户空间的指针所指向的地址的确是一段该进程本身的地址,而不是指向了不属于它的地方,而且每次都会拷贝一次数据,频繁访问内存,由于虚拟地址连续,物理地址不一定会连续,从而造成CPU的CACHE频繁失效,从而使速度降低。
简单来说,速度的降低实际在于每次拷贝都会做的合法性检查。
那么为了提高速度,还有一个方法就是使用mmap
函数。
mmap仅在第一次使用时为进程建立页表,也就是将一段物理地址映射到一段虚拟地址上,以后操作时不再检测其地址的合法性(合法性交由CPU页保护异常来做),另一方面是内核下直接操作mmap地址,可以不用频繁拷贝,也就是说在内核下直接可用指针向该地址操作,而不再在内核中专门开一个缓冲区,然后将缓冲区中的数据拷贝一次进来,mmap一般是将一段连续的物理地址映射成一段虚拟地址,当然,也可以将每段连续,但各段不连续的物理地址映射成一段连续的虚拟地址,无论如何,其物理地址在每段之中是连续的,这样一来,就不会造成CPU的CACHE频繁失效,从而大大节约时间
简单来说就是共享内存机制,允许多个进程共享相同的存储区,这样数据就不需要来回复制,全靠指针的方式读写这一段内存,因此内核态对内存的读写也将直接反映到用户态下。
这样就在看一个函数系列shm*
,mmap
实现共享内存是通过映射普通文件机制实现的,而shmat
则是通过系统共享内存机制实现。
1、mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存);缺点:进程间读取和写入速度要比主存的要慢。
2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)
使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用mmap。
简单来说:mmap
是文件操作,shm*
是内存操作。
那需求也很简单,就像先前的netlink的操作一样,快速把一个新的进程信息传输到用户态下。
本来找到一个轮子:Linux 进程通信之:内存共享(Shared Memory),这里面的代码挺好的实现了进程间共享内存,但是问题在于是通过shm
来实现的,这个方法没法在内核和用户态之间使用,因此还是回到mmap()
上才行。这是因为在linux中,用户内存
和内核内存
是独立的,在各自的地址空间。
看一下对于mmap
的解释:
map操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。
也就是说文件被映射进内存,映射后进程访问文件就可以像访问内存一样,无需read()
或者write()
等操作,而共享是因为此文件被同时映射而已。
而核心实现还是把一个虚拟内存区
映射到指定page
开始的连续物理页
上。
代码上通过一个轮子改一改,但是要思考的是,内核模块把信息写入一个buff
中,用户程序读取buff
,这是异步的,就是说二者互不干扰,只不过能同时对一个buff
作操作而已,因此如何让信息同步就是一个比较费脑筋的问题,但这是设计上的问题,而非代码。