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机制,查了一下例子:
#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,用不了;即使用得了,上诉第二个问题也是无法解决的。所以最后只能自己写驱动。