linux 文件映射 到共享内存,2--共享内存的实践到内核--共享内存的映射 - 如何从应用程序进入linux内核 - 无......

今天继续我们的内核研究,即然我们选择了2.6.26内核做为研究对象的版本,所以请朋友们还是手中有这个源代码比较好,有的地方可能需要朋友们自己去研读,特别是有一定基础的朋友可以更宽泛一些的阅读,如果你是新手就请跟着我的步骤进行,必竟主线我们是牢牢抓住不放的,好了,接着上一节我们谈到的应用程序,里面有这样一句代码:

shmat(shmid, (void *)0, 0);

这个函数就是将我们前边建立的共享内存映射到了本进程,我们跟着进入内核看一下,首先还是在sys_ipc()函数处

case SHMAT:

switch (version) {

default: {

ulong raddr;

ret = do_shmat (first, (char __user *) ptr, second, &raddr);

if (ret)

return ret;

return put_user (raddr, (ulong __user *) third);

}

case 1:    /* iBCS2 emulator entry point */

if (!segment_eq(get_fs(), get_ds()))

return -EINVAL;

/* The "(ulong *) third" is valid _only_ because of the kernel segment thing */

return do_shmat (first, (char __user *) ptr, second, (ulong *) third);

}

看起很复杂重要的函数只有一个那就是do_shmat(),它在ipc/shm.c中的816行处,我们看一下它,函数比较长,我们分段看它

long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr)

{

struct shmid_kernel *shp;

unsigned long addr;

unsigned long size;

struct file * file;

int err;

unsigned long flags;

unsigned long prot;

int acc_mode;

unsigned long user_addr;

struct ipc_namespace *ns;

struct shm_file_data *sfd;

struct path path;

mode_t f_mode;

err = -EINVAL;

if (shmid < 0)

goto out;

else if ((addr = (ulong)shmaddr)) {

if (addr & (SHMLBA-1)) {

if (shmflg & SHM_RND)

addr &= ~(SHMLBA-1);     /* round down */

else

#ifndef __ARCH_FORCE_SHMLBA

if (addr & ~PAGE_MASK)

#endif

goto out;

}

flags = MAP_SHARED | MAP_FIXED;

} else {

if ((shmflg & SHM_REMAP))

goto out;

flags = MAP_SHARED;

}

if (shmflg & SHM_RDONLY) {

prot = PROT_READ;

acc_mode = S_IRUGO;

f_mode = FMODE_READ;

} else {

prot = PROT_READ | PROT_WRITE;

acc_mode = S_IRUGO | S_IWUGO;

f_mode = FMODE_READ | FMODE_WRITE;

}

if (shmflg & SHM_EXEC) {

prot |= PROT_EXEC;

acc_mode |= S_IXUGO;

}

我们看到参数中第一个参数是上一节中讲到已经建立的共享内存的id标识号,第二、三个参数在应用程序分别传过一个0指针和0,第四个参数就是用于返回到sys_ipc()函数用的一个指针。函数中首先是对第二个参数也就是共享内存的映射地址进行检查,看是否能被SHMLBA整除,即是否按页的大小对齐。如果不能被整除这里就会进行对齐处理,我们看到应用程序传递过来的参数是0指针,这里就会由内核分配一个地址,此后就是对标志的一些处理。我们接着往下看

/*

* We cannot rely on the fs check since SYSV IPC does have an

* additional creator id...

*/

ns = current->nsproxy->ipc_ns;

shp = shm_lock_check(ns, shmid);

if (IS_ERR(shp)) {

err = PTR_ERR(shp);

goto out;

}

err = -EACCES;

if (ipcperms(&shp->shm_perm, acc_mode))

goto out_unlock;

err = security_shm_shmat(shp, shmaddr, shmflg);

if (err)

goto out_unlock;

path.dentry = dget(shp->shm_file->f_path.dentry);

path.mnt = shp->shm_file->f_path.mnt;

shp->shm_nattch++;

size = i_size_read(path.dentry->d_inode);

shm_unlock(shp);

这段代码中,通过shm_lock_check()函数找到struct shmid_kernel 结构,并赋值给这里的局部结构变量shp,我们看到shm_lock_check()函数是与ipc机制相关的,这里我们看一下

static inline struct shmid_kernel *shm_lock_check(struct ipc_namespace *ns,

int id)

{

struct kern_ipc_perm *ipcp = ipc_lock_check(&shm_ids(ns), id);

if (IS_ERR(ipcp))

return (struct shmid_kernel *)ipcp;

return container_of(ipcp, struct shmid_kernel, shm_perm);

}

函数内部与消息队列的函数 msg_lock_check()一模一样,无非找到我们已经建立的共享内存并上锁。我们已经在消除队列中讲过 msg_lock_check()函数,所以这里不跟进了。此后我们进行了一些权限的检查,这些检查与文件系统的权限检查类似,我们以后在看文件系统的时候就会讲到,下面是关键的部分了

err = -ENOMEM;

sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);

if (!sfd)

goto out_put_dentry;

file = alloc_file(path.mnt, path.dentry, f_mode, &shm_file_operations);

if (!file)

goto out_free;

file->private_data = sfd;

file->f_mapping = shp->shm_file->f_mapping;

sfd->id = shp->shm_perm.id;

sfd->ns = get_ipc_ns(ns);

sfd->file = shp->shm_file;

sfd->vm_ops = NULL;

down_write(&current->mm->mmap_sem);

if (addr && !(shmflg & SHM_REMAP)) {

err = -EINVAL;

if (find_vma_intersection(current->mm, addr, addr + size))

goto invalid;

/*

* If shm segment goes below stack, make sure there is some

* space left for the stack to grow (at least 4 pages).

*/

if (addr < current->mm->start_stack &&

addr > current->mm->start_stack - size - PAGE_SIZE * 5)

goto invalid;

}

user_addr = do_mmap (file, addr, size, prot, flags, 0);

*raddr = user_addr;

err = 0;

if (IS_ERR_VALUE(user_addr))

err = (long)user_addr;

invalid:

up_write(&current->mm->mmap_sem);

fput(file);

out_nattch:

down_write(&shm_ids(ns).rw_mutex);

shp = shm_lock_down(ns, shmid);

BUG_ON(IS_ERR(shp));

shp->shm_nattch--;

if(shp->shm_nattch == 0 &&

shp->shm_perm.mode & SHM_DEST)

shm_destroy(ns, shp);

else

shm_unlock(shp);

up_write(&shm_ids(ns).rw_mutex);

out:

return err;

out_unlock:

shm_unlock(shp);

goto out;

out_free:

kfree(sfd);

out_put_dentry:

dput(path.dentry);

goto out_nattch;

}

看似上面的过程很复杂其实实质作用的函数只有一个do_mmap()函数,这个函数与内存管理的映射相关,我们先预先看一下为好,也好了解一下内存映射的过程这个函数在include/linux/mm.h的1098行,函数非常短

static inline unsigned long do_mmap(struct file *file, unsigned long addr,

unsigned long len, unsigned long prot,

unsigned long flag, unsigned long offset)

{

unsigned long ret = -EINVAL;

if ((offset + PAGE_ALIGN(len)) < offset)

goto out;

if (!(offset & ~PAGE_MASK))

ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);

out:

return ret;

}

我们看到他转交给了do_mmap_pgoff()函数,它在mm/mmap.c的908行处,这个函数也很长,我们将在内存管理的分析中详细论述,这里只看其关键的部分,也就是我们应用程序要去的地方

unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,

unsigned long len, unsigned long prot,

unsigned long flags, unsigned long pgoff)

{

。。。。。。

addr = get_unmapped_area(file, addr, len, pgoff, flags);

我们的映射地址是0,所以内核会get_unmapped_area自动分配一个地址,这个函数我们不跟进了,它里面直接相关内存管理的细节,我们放一放,在do_mmap_pgoff函数中会再进一步调用

return mmap_region(file, addr, len, flags, vm_flags, pgoff,

accountable);

这是关键的映射地方,我们跟进看一看

unsigned long mmap_region(struct file *file, unsigned long addr,

unsigned long len, unsigned long flags,

unsigned int vm_flags, unsigned long pgoff,

int accountable)

{

我们只看关键的部分。。。。。。

if (file) {

error = -EINVAL;

if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))

goto free_vma;

if (vm_flags & VM_DENYWRITE) {

error = deny_write_access(file);

if (error)

goto free_vma;

correct_wcount = 1;

}

vma->vm_file = file;

get_file(file);

error = file->f_op->mmap(file, vma);

if (error)

goto unmap_and_free_vma;

if (vm_flags & VM_EXECUTABLE)

added_exe_file_vma(mm);

} else if (vm_flags & VM_SHARED) {

error = shmem_zero_setup(vma);

if (error)

goto free_vma;

}

实际最起作用的就只有上面红色的部分代码,这里的file也就是我们上一节讲到创建共享内存中在共享内存的文件系统中创建的那个文件指针,这里就取得了共享内存文件系统file指针后实际上就通过文件系统的file_operations钩子函数,为什么叫钩子函数,其实这些函数就是一些指针即地址,我们之所以称之为钩子就是因为经常要有东西往上挂,即把自己的地址放在file_operations中,这些地址是函数的地址,当代码访问到file_operations中的这些地址时就自动跳到我们挂到钩子上的函数中去了。更详细的要等到我们论述文件系统时了,我们先知道有这么一个钩子函数就成了,这里会执行到哪里呢,肯定是会执行到共享内存文件系统中的钩子函数中,我们看看他到了哪里,我们看到在ipc/shm.c的322行处有共享内存这个钩子函数的定义

static const struct file_operations shmem_file_operations = {

.mmap  = shmem_mmap,

#ifdef CONFIG_TMPFS

.llseek  = generic_file_llseek,

.read  = shmem_file_read,

.write  = do_sync_write,

.aio_write = generic_file_aio_write,

.fsync  = simple_sync_file,

.splice_read = generic_file_splice_read,

.splice_write = generic_file_splice_write,

#endif

};

很明显他会跳转到shmem_mmap()函数去执行

static int shmem_mmap(struct file *file, struct vm_area_struct *vma)

{

file_accessed(file);

vma->vm_ops = &shmem_vm_ops;

vma->vm_flags |= VM_CAN_NONLINEAR;

return 0;

}

重要的关键点是vma->vm_ops = &shmem_vm_ops;这里出现了另一个内存管理中的虚拟空间的钩子函数

static struct vm_operations_struct shmem_vm_ops = {

.fault  = shmem_fault,

#ifdef CONFIG_NUMA

.set_policy     = shmem_set_policy,

.get_policy     = shmem_get_policy,

#endif

};

到这里我们可以看到实际上就已经映射完成了,可能朋友们会说怎么完成的,没看明白,要知道内核是一个环环相扣的架构,这里其实交给了内存管理的去处理了,那一部分我们以后会进一步论述和分析,在共享内存的映射这里只是建立了内存管理中的必要的映射机制,也就是把钩子函数与这里的共享内存挂上钩。使其在执行到关于共享内存的映射部分时自动到这里提供的机制中去进一步分配和处理,我们只能粗略的这样简介一下,真正发生映射发生时是在“缺页异常”,什么是缺页异常,我们说过内存是页为单位管理的,缺页就是指未建立管理的单位内存,我们以后论述这个名词,现在是假设内存管理中要映射并且走到了缺页异常处理程序中,会自动跳转到上面的钩子函数.fault = shmem_fault,根据这里跳转到shmem_fault()函数

static int shmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)

{

struct inode *inode = vma->vm_file->f_path.dentry->d_inode;

int error;

int ret;

if (((loff_t)vmf->pgoff << PAGE_CACHE_SHIFT) >= i_size_read(inode))

return VM_FAULT_SIGBUS;

error = shmem_getpage(inode, vmf->pgoff, &vmf->page, SGP_CACHE, &ret);

if (error)

return ((error == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS);

mark_page_accessed(vmf->page);

return ret | VM_FAULT_LOCKED;

}

我们看到他进一步从shmem_getpage中查找或者交换页,那是内存管理部分的内容了我们以后会讲述,我们暂且讲到这里,至些我们明白了共享内存的映射实际上最后是与内存管理密切相关的。有兴趣朋友可以自己顺着上面的函数看一下与内存管理的交换、分配机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值