操作系统: 03 2010存档


QEMU 和bochs严格意义上说不是虚拟机,是“模拟机”,他们把guest os执行的指令由软件来解析执行(软CPU),而xen一类的虚拟机是把指令交给CPU(真正的CPU)来执行。模拟比虚拟慢,但更灵活,在单cpu的机 器上可以模拟多CPU的环境,这是xen做不到的。


05年的时候我就试用过bochs,觉得还不错。但这次用它来装rhel as4 超级慢,配置网络也费了半天劲。于是改用QEMU,发觉确实比bochs好:


1. 速度比bochs快

2. 不用配置文件,命令行很方便

3. 配置网络更方便

4. 自带vncserver,远程访问方便

5. 更妙的是,QEMU可以直接使用bochs生成的虚拟硬盘文件,平滑迁移

6. 最近还听说kvm和QEMU配合威力巨大,不过资源有限,没来得及尝试


只用一行就启动了一个64位、4CPU的模拟机器,还启动了vnc和ne2000的模拟网卡:

qemu-system-x86_64 -smp 4 -m 1G -hda rhel4_x86.img -vnc localhost:0 -net nic,model=ne2k_pci -net user


Xen是通过修改内核代码实现的,所以很多在xen上测试通过的驱动换到真实机器上却不能工作了。

对于驱动开发,还是QEMU搭个虚拟机比较好。


=== 2010.3.25 ===

QEMU从0.12系列开始就不再支持kqemu,我估计自己做了优化,因为我看0.12的速度还行。

装了KVM以后,QEMU不再只是单进程单CPU,而是可以用上多个CPU,速度当然更快,但是配了KVM后也有副作用:不再是模拟,而是虚拟,虚拟机的问题会影响到主机。

我在KVM+QEMU的虚拟机上跑了个错误的驱动,最后不仅这台虚拟机,连主机都挂了。


=== 2010.4.20 ===

发现QEMU有个缺陷:我装完了虚拟系统,想加一块虚拟硬盘,于是qemu-img create了一个5G的文件,然后 -hdb 设为第二块硬盘,但是进入虚拟系统以后,不管我怎么fdisk,它就是认为这块新硬盘只有1G (我的虚拟系统是rhel5,不应该是OS的问题)。


作者:董昊 (要转载的同学帮忙把名字和博客链接http://oldblog.donghao.org/uii/带上,多谢了!)


linux 上,进程退出会自动flush文件,自动close fd,自动释放占用的内存空间,等等,这些我们都知道了,不用管它。设想我们有个应用,进程退出前要做一个删除文件的操作,但是万一这个进程被别人 kill了或者自己coredump了,那这个操作就完不成了,怎么办?


不要再拘泥于OS之上的应用程序了!我们做一个虚拟设备解决这个问题:做一个miscdevice,然后实现它file_operations里的flush方法,在里面加上删文件的操作。在启动应用进程时打开这个miscdevice,然后,不管什么情况,只要该进程挂了,就会调用flush(这是linux kernel做的,dirver不用实现),而flush就会执行删除文件的操作(这是要自己写到驱动里)。


下面是代码示例:


int sample_flush(struct file* file)

{

    down(&inode_mutex);


    if (file)

    {

        atomic_inc(&file->f_count);

       

        if (file->f_dentry)

        {

            dget(file->f_dentry)

           

            node = file->f_dentry->d_inode;

       

            if (node)

            {

                atomic_inc(&node->i_count);

               

                if (node->i_nlink)

                {

                    parent = file->f_dentry->d_parent;

                   

                    if (!IS_ERR(file->f_dentry))

                    {

                        if (parent->d_inode)

                            vfs_unlink(parent->d_inode, file->f_dentry);

                    }

                }

               

                iput(node);

            }

           

            dput(file->f_dentry);

        }


        atomic_dec(&file->f_count);

    }


    up(&inode_mutex);

}


struct file_operations sample_fops =

{

    .flush = sample_flush;

}


很多人会被上面的代码搞晕:这么如此多的get/put和down/up?没办法,这就是内核代码,要应付各种数据结构的引用记数,还要应付多核,真正核心的其实只有这个vfs_unlink而已。


这只是个例子,flush能做的不仅是删个文件,还可以断个连接,发个邮件,躲个猫猫......OS能做的它都能做,当然,驱动也要靠硬件运转,如果机器直接宕机,那这个操作肯定是做不了。

作者:董昊 (要转载的同学帮忙把名字和博客链接http://oldblog.donghao.org/uii/带上,多谢了!)


项目中遇到需求,要在内核里读取一个文件的头部内容,这个文件已经mmap了,内容都在内存里。我试了一个较为简洁可靠的方法:


sszie_t    sample_read(struct file* file, char __user* buff, loff_t* pos)

{

    struct page*    page;

    char*              buff;


    if (file->f_mapping)

    {

        if ((page = find_get_page(file->mapping, 0))

        {

            buff = page_address(page);

            if (buff)

            {

                /* you can read buff content now */

            }

        }

    }

}


find_get_page是从一堆page里找到偏移量为0的那个page,然后从struct page里拿到真正内容的内存地址。好像已经mmap的文件file->mapping才有内容。

作者:董昊 (要转载的同学帮忙把名字和博客链接http://oldblog.donghao.org/uii/带上,多谢了!)


ldd3上已经讲了如何开发linux下的驱动程序,怎么让该设备支持poll(和epoll),但是不够详细,这里给个例子。假设实现一个misc设备,为了实现poll,当然要有个wait_queue,注意,是dev带wait_queue,我一疏忽把wait_queue带到file上去了,调了半天才发现这个低级错误。


struct sample_dev

{

    struct miscdevice       misc;

    wait_queue_head_t    wait;

};


static struct sample_dev    s_dev;


s_dev这个设备现在既可以当miscdevice用,同时又有了wait_queue


struct file_operations sample_fops =

{

    .owner       = THIS_MODULE,

    .read         = sample_read,

    .write        = sample_write,

    .poll           = sample_poll,

};


static int __init init_sample(void)

{

    s_dev.misc.minor = MISC_DYNAMIC_MINOR;

    s_dev.misc.name = "poll_device_sample";

    s_dev.misc.fops  = &sample_fops;

    init_waitqueue_head(&s_dev.wait);

    return misc_register(&s_dev.misc);

}


static void __exit exit_sample(void)

{

    misc_unregister(&s_dev.misc);

}


这是设备的注册和注销。下面看sample_poll的做法,和ldd3上说的一样:


unsigned int sample_poll(struct file* file, poll_table* wait)

{

    unsigned int    mask = 0;


    poll_wait(file, &s_dev.wait, wait);


    /* if have something to read (代码省略)*/

        mask |= POLLIN;

    /* if have something to write (代码省略)*/

        mask |= POLLOUT;

    /* if some error occur (代码省略)*/

        mask |= POLLERR;


    return mask;

}


poll_wait是linux内核提供的,标准做法,所以最好这么用。在poll_wait里,current进程挂在了s_dev的wait_queue里,只有两种情况让他醒来:一个是poll系统调用超时(poll_table负责),另一个是读写唤醒他(后面的代码)。


sszie_t    sample_read(struct file* file, char __user* buff, loff_t* pos)

{

    /* do what you want to read(代码省略)*/

    wake_up_interruptible(&s_dev.wait); 

}


由于读走了一些数据,缓冲区(代码没有详细写)有位置了,可以往里面写了,上面标红的行便唤醒随眠的进程,他(进程)醒来后就到了poll_wait语句的后面,开始查看缓冲区并置mask,最后返回。sample_write也是同样的实现方式。


以上代码只是例子,并不完整,但原理已经充分。这样实现的设备已经可以支持poll和epoll调用,当然,epoll的原理更为复杂,和上面说的过程有一些差异,详情可以参考这里