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的原理更为复杂,和上面说的过程有一些差异,详情可以参考这里

需要监控某个目录下有没有文件增加或删除,当然不能用循环检测来做,那样太耗CPU,所以改用2.6.9内核支持的dir notify机制,查了一下例子:


        #define _GNU_SOURCE     /* needed to get the defines */

        #include <fcntl.h>      /* in glibc 2.2 this has the needed

                                           values defined */

        #include <signal.h>

        #include <stdio.h>

        #include <unistd.h>


        static volatile int event_fd;


        static void handler(int sig, siginfo_t *si, void *data)

        {

                event_fd = si->si_fd;

        }


        int main(void)

        {

                struct sigaction act;

                int fd;


                act.sa_sigaction = handler;

                sigemptyset(&act.sa_mask);

                act.sa_flags = SA_SIGINFO;

                sigaction(SIGRTMIN + 1, &act, NULL);


                fd = open(".", O_RDONLY);

                fcntl(fd, F_SETSIG, SIGRTMIN + 1);

                fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT);

                /* we will now be notified if any of the files

                   in "." is modified or new files are created */

                while (1) {

                        pause();

                        printf("Got event on fd=%d\n", event_fd);

                }

        }


我试着在项目里用了一下,发现不行,有两个大毛病:

1. 信号机制会打断程序运行,epoll调用也会被打断,虽然还能“续上”,但毕竟影响太大

2. dnotify机制通知很快,我的创建文件后初始数据还没写进去,监控程序就发现并打开了文件


inotify是2.6.12才开始引入内核,而我用的是rhel4,用不了;即使用得了,上诉第二个问题也是无法解决的。所以最后只能自己写驱动。

关于存档

This page is an archive of entries from 03 2010 listed from newest to oldest.

02 2010 is the previous archive.

04 2010 is the next archive.

Find recent content on the main index or look in the 存档 to find all content.