分享

1--共享内存的实践到内核--共享内存的创建 - 如何从应用程序进入linux内核 - 无...

 jijo 2008-12-03







我是无名小卒,昨天在留言里看到说我日志抄袭的评论,我非常气愤,气愤那些抄袭我的文章而不做声明的人,这和明着偷窃有什么区别?假如有一天你的作品也被别人抄袭了你会如何?还有没有继续作品的信心呢,做为人的第一原则就应该诚实,所以希望阅读我日志的朋友不要把抄袭做成一种爱好和虚荣的表现,还是踏实的学习知识比抄袭强万倍,就象我以前认识的一个网友整天以与LINUX内核的专家在一起拍照与荣,动不动在网上炫耀与外国哪一个LINUX名人的合影,那兴奋劲感觉就象是他成了LINUX专家成了名人似的,或者在他的脑子里能与别人合影就会吸收别人的知识,我不知道他有没有这种特异功能,我只怀疑他是在热爱LINUX的学习呢还是热爱LINUX给他带来的虚荣,对,是虚荣心在作怪,他觉得利用LINUX的效应可以让他在事业上混个一官半职,天知道他脑袋里装的是学问还是水啊,就是学问我相信也是严重掺水了。所以希望抄袭我文章的朋友在看到这里时,请你把脑袋的水挤净,学知识不是为我学的,是为你自己学的,抄袭文章去炫耀只是你的虚荣心在张扬,一旦别人知道你抄袭后,或者有一天朋友们追究起来时,你的颜面何在?
 
今天我们继续linux从实践到内核的学习,我们开始看一下应用实践程序,他是关于共享内存的,程序来自于linux程序设计书中例子,利用他的例子是为了让大家知道什么是共享内存以及他有什么好处,相信朋友们跟我看我这个练习并走进内核就会清楚了,我们边分析边介绍,遇河搭桥,引领分析方法
首先我们建立一个描述共享内存的数据结构,我们把他放在一个共享的头文件shm_com.h中
 

#define TEXT_SZ 2048

struct shared_use_st {
    int written_by_you;
    char some_text[TEXT_SZ];
};

我们看到some_text数组是封装共享内存文字的的一个数组,另一个written_by_you是控制写进程(我们称它为生产进程吧)读进程(我们称它为消费进程吧),我们的比喻就象生产与消费的过程一样。我们先来看消费进程方向看一个代码,它包括创建共享内存的函数

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/shm.h>

#include "shm_com.h"

int main()
{
    int running = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    int shmid;

    srand((unsigned int)getpid());

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);

    if (shmid == -1) {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }

    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1) {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }

    printf("Memory attached at %X\n", (int)shared_memory);


    shared_stuff = (struct shared_use_st *)shared_memory;
    shared_stuff->written_by_you = 0;
    while(running) {
        if (shared_stuff->written_by_you) {
            printf("You wrote: %s", shared_stuff->some_text);
            sleep( rand() % 4 ); 
            shared_stuff->written_by_you = 0;
            if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
                running = 0;
            }
        }
    }

    if (shmdt(shared_memory) == -1) {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }

    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

我们看到上面的代码中,通过shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
创建一个共享内存,进一步使用shmat(shmid, (void *)0, 0);与建立的共享内存建立联系并把共享内存的地址赋值给void类型的指针shared_memory,此后将这个指针转变成struct shared_use_st *结构指针并把地址赋值给了shared_stuff,此后对shared_stuff 的操作就是对我们建立的共享内存的操作,我们分段看一下

shared_stuff = (struct shared_use_st *)shared_memory;
    shared_stuff->written_by_you = 0;
    while(running) {
        if (shared_stuff->written_by_you) {
            printf("You wrote: %s", shared_stuff->some_text);
            sleep( rand() % 4 ); 

            shared_stuff->written_by_you = 0;
            if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
                running = 0;
            }
        }
    }

在上面的while循环中,我们知道变量running是1,所以是个无限循环,循环中通过判断我们提到的written_by_you来控制共享内存的生产与消费,即写与读,我们看到消费程序在这里调用了sleep来让生产进程休眠的目的。这里rand()函数使用的是线性方法获得(伪)随机数。我们看到退出while循环的情况只有一种那就是共享内存读到end时。

if (shmdt(shared_memory) == -1) {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }

    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }

最后共享内存与进程断开联系shmdt(shared_memory),并且删除共享内存shmctl(shmid, IPC_RMID, 0)。

我们再看另一个生产进程的程序代码,这个进程允许我们输入数字给上面的消费进程

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/shm.h>

#include "shm_com.h"

int main()
{
    int running = 1;
    void *shared_memory = (void *)0;
    struct shared_use_st *shared_stuff;
    char buffer[BUFSIZ];
    int shmid;

    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);

    if (shmid == -1) {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }

    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1) {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }

    printf("Memory attached at %X\n", (int)shared_memory);

    shared_stuff = (struct shared_use_st *)shared_memory;
    while(running) {
        while(shared_stuff->written_by_you == 1) {
            sleep(1);
            printf("waiting for client...\n");
        }
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        
        strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
        shared_stuff->written_by_you = 1;

        if (strncmp(buffer, "end", 3) == 0) {
                running = 0;
        }
    }

    if (shmdt(shared_memory) == -1) {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

我们不用细说了,与消费进程的代码很相似,只是明白在循环中关键的是把我们在终端上输入的文字复制到了共享内存的结构变量shared_stuff中的some_text中,这个结构我们一开始就介绍了,大家肯定有印象,它的大小是2K,也看到循环中调用了sleep休眠函数是为了让消费进程读走文字。我们执行他

$gcc -o shm1 share1.c
$gcc -o shm2 share2.c

[root@localhost wumingxiaozu]#./shm1 &

[1] 3289
[root@localhost wumingxiaozu]# Memory attached at B7FAE000

[root@localhost wumingxiaozu]# ./shm2
Memory attached at B7FB2000
Enter some text: wumingxiaozu
You wrote: wumingxiaozu
waiting for client...
waiting for client...
waiting for client...
Enter some text: wumingxiaozu
You wrote: wumingxiaozu
waiting for client...
waiting for client...
Enter some text: end
[root@localhost wumingxiaozu]# You wrote: end

可以看到我们的程序是完全按照上面的分析运行的,今天先到这里明天继续。。。。。。

我们今天继续探讨共享内存的到底是如何进入内核及内核如何处理的,以前我们讲过消息队列的应用程序,没有看过这篇文章的朋友可以返回仔细阅读一下,我们这里不重复关于sys_ipc()函数的分析和介绍了,直接进入其关键的代码,在开始之前我们还是看一下应用程序中的调用界面,调用界面的叫法是为了说明函数的调用方式,上面有一个shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);这个是创建共享内存函数,我们顺着他进入sys_ipc()函数看关键部分:

    case SHMGET:
        return sys_shmget (first, second, third);

对照应用程序中的第一个参数传递下来的是1234,第三个参数是数据结构的大小,第三个结构是创建标志和权限。我们进入sys_shmget()

asmlinkage long sys_shmget (key_t key, size_t size, int shmflg)
{
    struct ipc_namespace *ns;
    struct ipc_ops shm_ops;
    struct ipc_params shm_params;

    ns = current->nsproxy->ipc_ns;

    shm_ops.getnew = newseg;
    shm_ops.associate = shm_security;
    shm_ops.more_checks = shm_more_checks;

    shm_params.key = key;
    shm_params.flg = shmflg;
    shm_params.u.size = size;

    return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params);
}

看一下这个函数的参数是不是与我们之前预测的一样,尽管之前用first,second,third名称,函数原形这里就定义的非常明确了,我们分析一下这个函数吧,首先是找到当前进程的ipc空间结构,然后对声明的局部变量struct ipc_ops shm_ops即ipc机制的操作函数结构变量的初始化,再者把参数传递来的数值初始化给局部变量struct ipc_params shm_params,这里与消息队列的创建函数基本相同,最后传递给ipcget()函数处理。这个函数在消息队列的创建一节中我们分析过了,这里不重复了,其主要的目的就是建立一个ipc的管理机制,朋友们可以返回消息队列那节阅读我们只关心在ipcget()函数中会执行一个关键的过程那也就是我们在这里看到的 shm_ops.getnew = newseg;即newseg()函数来创建共享内存,而消息队列是执行的newque()。

static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
{
    key_t key = params->key;
    int shmflg = params->flg;
    size_t size = params->u.size;
    int error;
    struct shmid_kernel *shp;
    int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFT;
    struct file * file;
    char name[13];
    int id;

    if (size < SHMMIN || size > ns->shm_ctlmax)
        return -EINVAL;

    if (ns->shm_tot + numpages > ns->shm_ctlall)
        return -ENOSPC;

    shp = ipc_rcu_alloc(sizeof(*shp));
    if (!shp)
        return -ENOMEM;

    shp->shm_perm.key = key;
    shp->shm_perm.mode = (shmflg & S_IRWXUGO);
    shp->mlock_user = NULL;

    shp->shm_perm.security = NULL;
    error = security_shm_alloc(shp);
    if (error) {
        ipc_rcu_putref(shp);
        return error;
    }

    sprintf (name, "SYSV%08x", key);
    if (shmflg & SHM_HUGETLB) {
        /* hugetlb_file_setup takes care of mlock user accounting */
        file = hugetlb_file_setup(name, size);
        shp->mlock_user = current->user;
    } else {
        int acctflag = VM_ACCOUNT;
        /*
         * Do not allow no accounting for OVERCOMMIT_NEVER, even
          * if it's asked for.
         */

        if ((shmflg & SHM_NORESERVE) &&
                sysctl_overcommit_memory != OVERCOMMIT_NEVER)
            acctflag = 0;
        file = shmem_file_setup(name, size, acctflag);
    }
    error = PTR_ERR(file);
    if (IS_ERR(file))
        goto no_file;

    id = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);
    if (id < 0) {
        error = id;
        goto no_id;
    }

    shp->shm_cprid = task_tgid_vnr(current);
    shp->shm_lprid = 0;
    shp->shm_atim = shp->shm_dtim = 0;
    shp->shm_ctim = get_seconds();
    shp->shm_segsz = size;
    shp->shm_nattch = 0;
    shp->shm_file = file;
    /*
     * shmid gets reported as "inode#" in /proc/pid/maps.
     * proc-ps tools use this. Changing this will break them.
     */

    file->f_dentry->d_inode->i_ino = shp->shm_perm.id;

    ns->shm_tot += numpages;
    error = shp->shm_perm.id;
    shm_unlock(shp);
    return error;

no_id:
    fput(file);
no_file:
    security_shm_free(shp);
    ipc_rcu_putref(shp);
    return error;
}

我们看一下这个关键的创建函数,首先是重新取得传递过来的数值,根据传递过来的要求大小计算出所需要的页面数,这里朋友们可能不熟悉什么是页的概念,不要紧先放一放,等到我们讲到内核的内部构造时自然会详细论述,大家只需要先了解有这么一种概念即内存是用页来管理的,要想使用内存必须申请,即申请页的单位。我们暂且只知道这么多就够了,此后在上面的函数中对大小进行一些检查,这里有一个关键的数据结构struct shmid_kernel *shp;

struct shmid_kernel /* private to the kernel */
{    
    struct kern_ipc_perm    shm_perm;
    struct file *        shm_file;
    unsigned long        shm_nattch;
    unsigned long        shm_segsz;
    time_t            shm_atim;
    time_t            shm_dtim;
    time_t            shm_ctim;
    pid_t            shm_cprid;
    pid_t            shm_lprid;
    struct user_struct    *mlock_user;
};

很明确这个数据结构是内核用来描述共享内存专用的。但是这里有一个file 的结构指针,这个指针是关于文件系统的,可能朋友们不了解,还是先放一放只需要知道有文件系统的这个概念,为什么这里有一个相关于文件系统的数据结构指针,毛德操老师在他的linux内核情景分析中曾经解释为共享内存也是以页面在内存中接受内存管理机制的调试,也会换入和换出,至于为什么有这个文件的指针是为了共享内存也是以文件映射的方式与进程关联的。为此内核为共享内存建立了一个临时的文件系统,代码在mm/shmem.c中的2456行处

static struct file_system_type tmpfs_fs_type = {
    .owner        = THIS_MODULE,
    .name        = "tmpfs",
    .get_sb        = shmem_get_sb,
    .kill_sb    = kill_litter_super,
};

每一个文件系统都要有这么一个类型声明,可以看出这里声明为tmpfs,注意我们分析的代码是目前发行的最新的2.6.26,如果朋友们想读代码一定要注意版本,linux在系统初始化时会通过init_tmpfs()来安装这个共享内存文件系统

static int __init init_tmpfs(void)
{
    int error;

    error = bdi_init(&shmem_backing_dev_info);
    if (error)
        goto out4;

    error = init_inodecache();
    if (error)
        goto out3;

    error = register_filesystem(&tmpfs_fs_type);
    if (error) {
        printk(KERN_ERR "Could not register tmpfs\n");
        goto out2;
    }

    shm_mnt = vfs_kern_mount(&tmpfs_fs_type, MS_NOUSER,
                tmpfs_fs_type.name, NULL);
    if (IS_ERR(shm_mnt)) {
        error = PTR_ERR(shm_mnt);
        printk(KERN_ERR "Could not kern_mount tmpfs\n");
        goto out1;
    }
    return 0;

out1:
    unregister_filesystem(&tmpfs_fs_type);
out2:
    destroy_inodecache();
out3:
    bdi_destroy(&shmem_backing_dev_info);
out4:
    shm_mnt = ERR_PTR(error);
    return error;
}

关于文件系统的探述在上面提过我们会在文件系统部分进行分析,这里只看一下而已,回到我们的主题避免把话题扯的太远,我们只需要知道我们在这里已经有了共享内存的文件系统,再看我们的newseg()创建共享内存的函数,我们看到实际上共享内存的建立转化为在共享内存的文件系统的建立映射文件的问题,它是由shmem_file_setup()函数来完成的,这个函数在mm/shmem.c中的2509行

struct file *shmem_file_setup(char *name, loff_t size, unsigned long flags)
{
    int error;
    struct file *file;
    struct inode *inode;
    struct dentry *dentry, *root;
    struct qstr this;

    if (IS_ERR(shm_mnt))
        return (void *)shm_mnt;

    if (size < 0 || size > SHMEM_MAX_BYTES)
        return ERR_PTR(-EINVAL);

    if (shmem_acct_size(flags, size))
        return ERR_PTR(-ENOMEM);

    error = -ENOMEM;
    this.name = name;
    this.len = strlen(name);
    this.hash = 0; /* will go */
    root = shm_mnt->mnt_root;
    dentry = d_alloc(root, &this);
    if (!dentry)
        goto put_memory;

    error = -ENFILE;
    file = get_empty_filp();
    if (!file)
        goto put_dentry;

    error = -ENOSPC;
    inode = shmem_get_inode(root->d_sb, S_IFREG | S_IRWXUGO, 0);
    if (!inode)
        goto close_file;

    SHMEM_I(inode)->flags = flags & VM_ACCOUNT;
    d_instantiate(dentry, inode);
    inode->i_size = size;
    inode->i_nlink = 0;    /* It is unlinked */
    init_file(file, shm_mnt, dentry, FMODE_WRITE | FMODE_READ,
            &shmem_file_operations);
    return file;

close_file:
    put_filp(file);
put_dentry:
    dput(dentry);
put_memory:
    shmem_unacct_size(flags, size);
    return ERR_PTR(error);
}

这个函数直接与文件系统相关联,有朋友可能会问这么复杂啊,其实内核就是环环相扣在一起的一个知识库,我们遇到很多朋友认为只要掌握了某一部分就行了,比如网络文件系统,其实内核的很多知识点都是交叉在一起的,我们只掌握其一是远远不够的,不过朋友们大可放心,随着我们的逐渐深入内核会把这些问题都解释清楚的,这里就放一放吧,考虑大多数朋友的困难原因,有能力朋友可以顺着这个函数去看一下,总之这个函数就是为了在共享内存的文件系统中建立起我们声明的1234这个为名称的一个文件并且建立了操作这个文件的一系列的机制。于是我们建立共享内存实际转化成了建立一个文件并映射给进程。这好象很多朋友不可思议的事,事实就是在我们意料之外,linux把许多事情都变的神奇,这也为什么全球那么多超级计算机和巨型计算机使用linux系统的原因,为了不扯太远我们只说这么多。总之,以后对共享内存的操作实际上就是对这里建立的文件的操作,中间是由上面的函数建立的文件系统的操作机制来完成的。回到我们的newseg()函数中,建立的这个file文件指针最终保存在共享内存的描述结构struct shmid_kernel *shp中,这个shp结构变量是通过ipc_addid()函数的ipc机制建立联系。最后函数通过error = shp->shm_perm.id;return error返回共享内存的id号。至于ipc_add函数我们以前在消息队列中读过了,其他的代码朋友们可以酌情阅读,我们的主线已经理清了,共享内存的创建完成了。







    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多