qemu
1、官网
官网:http://wiki.qemu.org/Main_Page 官网:https://www.qemu.org/contribute/ 代码仓库:https://gitlab.com/qemu-project/qemu
15.7 MB Files 805.9 GB Storage 不能被这些数字迷惑,实际下载大小约为211.87 MiB(20210526) git clone https://gitlab.com/qemu-project/qemu.git
v5.0.0 Apr 29, 2020 v5.0.0-rc0 Mar 25, 2020 v2.5.1 Mar 30, 2016
基本上都是x.0.0~x.2.0版本命名方式
一次有趣的代码贡献:https://gitlab.com/qemu-project/qemu/-/issues/491
2、简介
VMWare Workstation 就是这样一个可以完美满足我要求的桌面用户最满意的虚拟机。我经常使用它来折腾各个 Linux 发行版,而且运行流畅。当然,在 Linux 这个开源的世界我们是不该去使用破解版这样的东西的。不过不用担心,在 Linux 江湖中,还有 VirtualBox、QEMU 这样的虚拟机软件可用。
QEMU 本身是一个非常强大的虚拟机,甚至在 Xen、KVM 这些虚拟机产品中都少不了 QEMU 的身影。在 QEMU 的官方文档中也提到,QEMU 可以利用 Xen、KVM 等技术来加速。为什么需要加速呢,那是因为如果单纯使用 QEMU 的时候,它自己模拟出了一个完整的个人电脑,它里面的 CPU 啊什么的都是模拟出来的,它甚至可以模拟不同架构的 CPU,比如说在使用 Intel X86 的 CPU 的电脑中模拟出一个 ARM 的电脑或 MIPS 的电脑,这样模拟出的 CPU 的运行速度肯定赶不上物理 CPU。使用加速以后呢,可以把客户操作系统的 CPU 指令直接转发到物理 CPU,自然运行效率大增。
QEMU 同时也是一个非常简单的虚拟机,给它一个硬盘镜像就可以启动一个虚拟机,如果想定制这个虚拟机的配置,比如用什么样的 CPU 啊、什么样的显卡啊、什么样的网络配置啊,指定相应的命令行参数就可以了。它支持许多格式的磁盘镜像,包括 VirtualBox 创建的磁盘镜像文件。它同时也提供一个创建和管理磁盘镜像的工具 qemu-img。QEMU 及其工具所使用的命令行参数,直接查看其文档即可。
Intel Core i7-4770K 的 CPU,虚拟出的 XP 也分配了 2G 的内存和两个 CPU,但是流畅度仍较差。说明单纯使用 QEMU 还是不能满足我们桌面用户的需要。配合Xen 或者 KVM 呢?性能是否会有质的飞跃?
QEMU 是一个强大的虚拟机软件,它可以完全以软件的形式模拟出一台完整的电脑所需的所有硬件,甚至是模拟出不同架构的硬件,在这些虚拟的硬件之上,可以安装完整的操作系统。 强烈推荐这篇文章:https://www.cnblogs.com/sunylat/p/6217536.html
为了提高虚拟机软件的性能,开发者们各显神通。其中,最常用的办法就是在主操作系统中通过内核模块开一个洞,通过这个洞将虚拟机中的操作直接映射到物理硬件上,从而提高虚拟机中运行的操作系统的性能。
在社区中,大家常把 KVM 和 Xen 相提并论,但是它们其实完全不一样。从上图可以看出,使用内核模块加速这种模式,主操作系统仍然占主导地位,内核模块只是在主操作系统中开一个洞,用来连接虚拟机和物理硬件,给虚拟机加速,但是虚拟机中的客户操作系统仍然受到很大的限制。这种模式比较适合桌面用户使用,主操作系统仍然是他们的主战场,不管是办公还是打游戏,都通过主操作系统完成,客户操作系统只是按需使用。至于 Xen,则完全使用不同的理念,比较适合企业级用户使用,桌面用户就不要轻易去碰了。
guest----host
DKMS简介 我们都知道,如果要使用没有集成到内核之中的Linux驱动程序需要手动编译。当然,这并不是一件什么难事,即使是对于没有编程经验的Linux使用者,只要稍微有点hacker的意识,努力看看代码包里的Readme或者INSTALL文件,按部就班的执行几条命令还是很容易办到的。但这里还有一个问题,Linux模块和内核是有依赖关系的,如果遇到因为发行版更新造成的内核版本的变动,之前编译的模块是无法继续使用的,我们只能手动再编译一遍。这样重复的操作有些繁琐且是反生产力的,而对于没有内核编程经验的使用者来说可能会造成一些困扰,使用者搞不清楚为什么更新系统之后,原来用的好好的驱动程序突然就不能用了。这里,就是Dell创建的DKMS项目的意义所在。DKMS全称是Dynamic Kernel Module Support,它可以帮我们维护内核外的这些驱动程序,在内核版本变动之后可以自动重新生成新的模块。
KVM 和 QEMU 是相辅相成的,QEMU 可以使用 KVM 内核模块加速,而 KVM 需要使用 QEMU 运行虚拟机。从上图可以看到,如果要使用 Ubuntu 的包管理软件安装 KVM,其实安装的就是 qemu-kvm。而 qemu-kvm 并不是一个什么很复杂的软件包,它只包含很少量几个文件,如下图: 用 man 命令查看一下它的文档,发现 qemu-kvm 包不仅包含的文件很少,而且它的可执行文件 kvm 也只是对 qemu-system-x86_64 命令的一个简单包装
可以这么说,如果没有 VirtualBox 的话,QEMU+KVM 的组合应该是桌面用户的首选。下一篇我将尝试 VirtualBox,VirtualBox 号称是最强大的开源虚拟机系统。
第三方虚拟机管理工具 virt-manager
而且 VirtualBox 并不仅仅适用于桌面用户,对于企业级的应用,它也是可以的。
dpkg -L 命令 Xen 具有非常高的难度,别说玩转,就算仅仅只是理解它,都不是那么容易。
在 Xen 中,则根本没有 Host System 的概念,传说它所有的虚拟机都直接运行于硬件之上,虚拟机运行的效率非常的高,虚拟机之间的隔离性非常的好。
当然,传说只是传说。我刚开始也是很纳闷,怎么可能让所有的虚拟机都直接运行于硬件之上。后来我终于知道,这只是一个噱头。虚拟机和硬件之间,还是有一个管理层的,那就是 Xen Hypervisor。当然 Xen Hypervisor 的功能毕竟是有限的,怎么样它也比不上一个操作系统,因此,在 Xen Hypervisor 上运行的虚拟机中,有一个虚拟机是具有特权的,它称之为 Domain 0,而其它的虚拟机都称之为 Domain U。
既然 Domain 0 也是一个虚拟机,也是被管理的对象,所以可以给它分配很少的资源,然后将其余的资源公平地分配到其它的 Domain。但是很奇怪的是,所有的虚拟机管理软件其实都是运行在这个 Domain 0 中的。同时,如果要连接到其它 Guest System 的控制台,而又不是使用远程桌面(VNC)的话,这些控制台也是显示在 Domian 0 中的。所以说,这是一个奇异的架构,是一个让人很不容易理解的架构。
这种架构桌面用户不喜欢,因为 Host System 变成了 Domain 0,本来应该掌控所有资源的主操作系统变成了一个受管理的虚拟机,本来用来打游戏、编程、聊天的主战场受到限制了,可能不能完全发挥硬件的性能了,还有可能运行不稳定了,自然会心里不爽。(Domain 0确实不能安装专用显卡驱动,确实会运行不稳定,这个后面会讲。)但是企业级用户喜欢,因为所有的 Domain 都是虚拟机,所以可以更加公平地分配资源,而且由于 Domain U 不再是运行于 Domian 0 里面的软件,而是和 Domain 0 平级的系统,这样即使 Domain 0 崩溃了,也不会影响到正在运行的 Domain U。(真的不会有丝毫影响吗?我表示怀疑。)
1.Xen 虚拟机不应该是桌面用户的首选,因为它架构比较奇异不容易理解,可能因内核升级而出现不稳定,不能充分发挥桌面硬件的性能,比如显卡;桌面用户还是应该首选 VirtualBox。
2.企业及客户可以考虑 Xen,因为它可以提供较好的性能和隔离性,企业级用户不需要桌面用户那么多的功能,所以可以把 Domain 0 做到很薄,可以完全不要图形界面,也不用经常升级内核,甚至可以选择一个经过修改优化的内核,这样就可以在一套硬件上运行尽可能多的虚拟机。
3、在qemu中模拟设备
https://zhuanlan.zhihu.com/p/57526565
MemoryRegion 存储区域
硬件模拟无外乎两个东西,一个是中断,一个是IO访问。
中断很简单,知道中断号,用qemu_set_irq()或者qemu_irq_pluse()往里种就可以了。
MemoryRegion:这表示一组面向Guest的,具有相同属性的内存区。后面简称MR。系统有全局的总MR,你直接用get_system_memory()就可以拿到了。所以你实际上任何时候都可以访问全局任何内存。
MemoryRegionCache:这表示一片为了满足Guest需要的一片临时的“真内存”。换句话说,MemoryRegion是描述一片内存区,MemoryRegionCache是真的要用的内存,Hypervisor根据需要动态申请,后面简称MRC。如果你不是要深入定制,一般你不管这个东西没有任何问题。
AddressSpace:这表示一个地址空间,一个地址空间可以包含多个不同属性的MR。后面简称AS。AS是和MR直接对应的,所以你可以直接用address_space_memory拿到对应get_system_memory()的AS。
FlatView:这表示看到的地址空间。这就比较绕了。这么说:AS是立体的,里面的MR是相互独立的,他们可以交叠,转义,动态开关等。但当你去访问的时候,某个时刻,某个物理地址总是对应着某个MR中的地址,FlatView用来表示层叠的结果。后面这个简称FV。FV大部分时候写设备模拟的时候都不用管,它是用于深入处理Host这边访问内存的时候用的,比如通过address_space_to_flatview(as)把as换成fv,然后用flatview_read/write()进行本地内存访问。
memory_region_init_io(&iomr, owner, ops, priv, name, size);
4、理解qemu中的xhci实现
qemu/hw/usb/hcd-xhci.c ->static void usb_xhci_realize(struct PCIDevice *dev, Error **errp)
api文档:https://qemu.readthedocs.io/en/latest/devel/memory.html
void memory_region_init_io(MemoryRegion *mr, Object *owner, const MemoryRegionOps *ops, void *opaque, const char *name, uint64_t size)
Initialize an I/O memory region.
Description
Accesses into the region will cause the callbacks in ops to be called. if size is nonzero, subregions will be clipped to size.
void memory_region_init_io(MemoryRegion *mr,
Object *owner,
const MemoryRegionOps *ops,
void *opaque,
const char *name,
uint64_t size)
{
memory_region_init(mr, owner, name, size);
mr->ops = ops ? ops : &unassigned_mem_ops;
mr->opaque = opaque;
mr->terminates = true;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void memory_region_add_subregion(MemoryRegion *mr, hwaddr offset, MemoryRegion *subregion)
Add a subregion to a container.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void memory_region_init(MemoryRegion *mr, Object *owner, const char *name, uint64_t size)
Initialize a memory region
qemu中:
#define MAXSLOTS 64
#define LEN_DOORBELL ((MAXSLOTS + 1) * 0x20)
static const MemoryRegionOps xhci_doorbell_ops = {
.read = xhci_doorbell_read,
.write = xhci_doorbell_write,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};
memory_region_init_io(&xhci->mem_doorbell, OBJECT(xhci), &xhci_doorbell_ops, xhci,
"doorbell", LEN_DOORBELL);
可以看出LEN_DOORBELL不为0
#define OFF_DOORBELL 0x2000
memory_region_add_subregion(&xhci->mem, OFF_DOORBELL, &xhci->mem_doorbell);
后面就太多了,发现这样也并没有什么意义。
memory_region_dispatch_write
memory_region_write_accessor
mr->ops->write(mr->opaque, addr, tmp, size);
[root@chroot <vtcompile> ~/buildenv/Trunk/source/app/qemu/qemu-2.5.1 ]#grep -R memory_region_dispatch_write
exec.c: result |= memory_region_dispatch_write(mr, addr1, val, 8,
exec.c: result |= memory_region_dispatch_write(mr, addr1, val, 4,
exec.c: result |= memory_region_dispatch_write(mr, addr1, val, 2,
exec.c: result |= memory_region_dispatch_write(mr, addr1, val, 1,
exec.c: r = memory_region_dispatch_write(mr, addr1, val, 4, attrs);
exec.c: r = memory_region_dispatch_write(mr, addr1, val, 4, attrs);
exec.c: r = memory_region_dispatch_write(mr, addr1, val, 2, attrs);
memory.c:MemTxResult memory_region_dispatch_write(MemoryRegion *mr,
hw/s390x/s390-pci-inst.c: memory_region_dispatch_write(mr, offset, data, len,
hw/s390x/s390-pci-inst.c: memory_region_dispatch_write(mr, env->regs[r3] + i * 8,
hw/vfio/pci-quirks.c.bak: memory_region_dispatch_write(&vdev->pdev.msix_table_mmio,
hw/vfio/pci-quirks.c: memory_region_dispatch_write(&vdev->pdev.msix_table_mmio,
Binary file x86_64-softmmu/qemu-system-x86_64 matches
Binary file x86_64-softmmu/memory.o matches
Binary file x86_64-softmmu/hw/vfio/pci-quirks.o matches
Binary file x86_64-softmmu/exec.o matches
Binary file x86_64-softmmu/cputlb.o matches
Binary file root@10.70.22.175 matches
include/exec/memory.h: * memory_region_dispatch_write: perform a write directly to the specified
include/exec/memory.h:MemTxResult memory_region_dispatch_write(MemoryRegion *mr,
softmmu_template.h: memory_region_dispatch_write(mr, physaddr, val, 1 << SHIFT,
5、追踪CR_STOP_ENDPOINT命令
gdb调试打印全部堆栈:
pidof kvm
gdb qemu-system-x86_64 -p 1234
b memory_region_dispatch_write(不推荐太多)
b xhci_process_commands
handle SIGPIPE nostop(必须要有这个,不然c后就断了)
c
(gdb) bt
#0 xhci_process_commands (xhci=0x5600a5a6e000) at hw/usb/hcd-xhci.c:2690
#1 0x000056009ea71881 in memory_region_write_accessor (mr=<optimized out>, addr=<optimized out>, value=<optimized out>, size=<optimized out>, shift=<optimized out>,
mask=<optimized out>, attrs=...) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:451
#2 0x000056009ea719cb in access_with_adjusted_size (addr=addr@entry=0, value=value@entry=0x7f656bbea958, size=size@entry=4, access_size_min=<optimized out>,
access_size_max=<optimized out>, access=access@entry=0x56009ea71850 <memory_region_write_accessor>, mr=mr@entry=0x5600a5a6ed50, attrs=attrs@entry=...)
at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:507
#3 0x000056009ea73400 in memory_region_dispatch_write (mr=mr@entry=0x5600a5a6ed50, addr=0, data=0, size=size@entry=4, attrs=attrs@entry=...)
at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:1159
#4 0x000056009ea2cb5e in address_space_rw (as=0x56009f421c80, addr=4227948544, attrs=..., buf=buf@entry=0x7f6580d6e028 "", len=4, is_write=true)
at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/exec.c:2551
#5 0x000056009ea704b8 in kvm_cpu_exec (cpu=cpu@entry=0x5600a3a48000) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/kvm-all.c:3123
#6 0x000056009ea57344 in qemu_kvm_cpu_thread_fn (arg=0x5600a3a48000) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/cpus.c:1066
#7 0x00007f657a2d6b50 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
#8 0x00007f657a020a7d in clone () from /lib/x86_64-linux-gnu/libc.so.6
#9 0x0000000000000000 in ?? ()
6、根据google查找原因
google:xhci: FIXME: endpoint stopped w/ xfers running, data might be lost
https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=980833
passthrough:透传;通过;直通;穿过;经过 stick:枝条;枯枝;柴火棍儿;球棍;条状物;棍状物
他使用了一个金士顿2.0的U盘(0951:1642 Kingston Technology DT101 G2)挂载到xhci主控,然后将U盘分区格式化成ext4文件系统。
fdisk -l
设备 启动 起点 末尾 扇区 大小 Id 类型
/dev/sda1 * 2048 61439999 61437952 29.3G 7 HPFS/NTFS/exFAT
设备 启动 起点 末尾 扇区 大小 Id 类型
/dev/sda1 * 2048 61439999 61437952 29.3G 7 HPFS/NTFS/exFAT
root@ubuntu180001:~# mkfs.ext4 /dev/sda1
mke2fs 1.44.1 (24-Mar-2018)
/dev/sda1 有一个 ntfs 文件系统
Proceed anyway? (y,N) y
/dev/sda1 已经挂载; 取消建立 文件系统 !
root@ubuntu180001:~# umount !$
umount /dev/sda1
root@ubuntu180001:~# mkfs.ext4 /dev/sda1
mke2fs 1.44.1 (24-Mar-2018)
/dev/sda1 有一个 ntfs 文件系统
Proceed anyway? (y,N) y
创建含有 7679744 个块(每块 4k)和 1921360 个inode的文件系统
文件系统UUID:a1a426ab-d82b-4dca-88e8-f3590bd056ce
超级块的备份存储于下列块:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000
正在分配组表: 完成
正在写入inode表: 完成
创建日志(32768 个块) 完成
写入超级块和文件系统账户统计信息: 已完成
然鹅并没有复现问题。
os-posix:273 kvm: -device nec-usb-xhci,id=xhci,bus=pci.0,addr=0x1b.0x0: 'nec-usb-xhci' is not a valid device model name
发现是qemu文件被重定向了,即qemu文件中没有放开xhci主控端口数量。
ovirt几种网卡(e1000, rtl8139, virtio)的简要说明
ovirt创建网卡时候有3种选择,分别是e1000, rtl8139, virtio。
“rtl8139”这个网卡模式是qemu-kvm默认的模拟网卡类型,RTL8139是Realtek半导体公司的一个10/100M网卡系列,是曾经非常流行(当然现在看来有点古老)且兼容性好的网卡,几乎所有的现代操作系统都对RTL8139网卡驱动的提供支持。 “e1000”系列提供Intel e1000系列的网卡模拟,纯的QEMU(非qemu-kvm)默认就是提供Intel e1000系列的虚拟网卡。 “virtio” 类型是qemu-kvm对半虚拟化IO(virtio)驱动的支持。 virtio网卡数据交换性能大幅度领先平台上的e1000网卡和rtl8139网卡,一般情况下都建议使用virtio网卡。e1000网卡虽然性能比较差,但是兼容性比较好,有时候需要定位问题就要将网卡切换成e1000。
这三个网卡的最大区别(此处指最需要关注的地方)是速度: rtl8139 10/100Mb/s e1000 1Gb/s virtio 10Gb/s
注意virtio是唯一可以达到10Gb/s的,根据文献一,其iperf测试速率可达到9.4。文献1还提到如果virtio配置不合理速度只能达到3.6。不用担心这个情况,经查验ovirt自己生成的虚拟机配置都是合理的。( use ‘-device virtio-net-pci’ option)
当然这并不意味着rtl8139和e1000是没有意义的,毕竟virtio需要在guest上面安装驱动。rtl8139和e1000在兼容的广泛度上占有优势。例如,如果ovirt的cpu不是x86,而是arm或者其它某些系列cpu,上述网卡未必全部支持。这点需要注意。
virtio-net-pci
7、在ubuntu下使用虚拟机
https://zhuanlan.zhihu.com/p/32125940https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/sect-graphic_user_interface_tools_for_guest_virtual_machine_management-remote_viewer
8、Freedesktop.org
致力于Linux和其他类Unix上的X窗口系统的桌面环境之间的互操作性和基础技术共享的项目 freedesktop.org(fd.o)是一个致力于Linux和其他类Unix上的X窗口系统的桌面环境之间的互操作性和基础技术共享的项目。 外文名Freedesktop.org成立时间2000年3月 由红帽的HavocPennington于2000年3月创立。 该组织关注用户。X有很多开发框架,将来很可能也这样。而该组织力求这其中的不同不会为最终用户所察觉。 最流行的开源X桌面——GNOME、KDE和Xfce都与该项目紧密合作。在2006年该项目发布了Portland1.0(xdg-utils),一个常用的桌面环境接口的集合。 freedesktop.org最初的名称叫XDesktopGroup(X桌面工作组),其缩写"XDG"在他们的工作中仍然经常被用到。
github:https://github.com/freedesktop/spice-usbredir
9、提高bulk设备传输速度
网络问题无法进行优化 bulk流只对in包有效,烧录是往设备里面写数据,因此是out包。 修改端口的maxsize会有枚举问题,并且实际上跟这个无关,因为设备又不是根据这个进行发包的。 可以修改设备描述符中maxsize值,可以提供映射速度。
10、pipeline变量
翻译成管道或者流水线。
AI回答:在QEMU中,USBEndpoint结构体中的pipeline变量通常用于控制USB数据包的传输流程。当pipeline变量为false时,表示数据包的传输是单向的,即只能进行数据的接收或发送。而当pipeline变量为true时,表示数据包的传输是双向的,即可以进行数据的接收和发送。这个变量的取值影响着USB数据包在端点中的传输方式,根据其取值不同,USB设备模拟会采取相应的传输逻辑来处理数据包的接收和发送。
不是很准确,通过代码来看pipeline默认为true时,bulk数据包会进行分包,把一个大包分成若干个小包,然后会进行分包和组包过程。 在combined-packet.c文件中usb_combined_input_packet_complete函数。
实践发现,设置pipeline为false后,整体数据包传输速度变慢,数据包大小也变小了。
11、键盘按键适配
11-1、qemu不同版本现状
KeyboardTest工具下载:https://www.passmark.com/products/keytest/index.php 通过qemu的qemu_input_map_qnum_to_qcode的qnum序列来查找 资料:https://aeb.win.tue.nl/linux/kbd/scancodes-6.html#msinternet
发现qemu2.5的Q_KEY_CODE_MAX = 125,而适配的韩语切换按键是242,因此在低版本的qemu无法进行适配。但确实也能在ui/sdl2-keymap.h搜索到Q_KEY_CODE_MAIL变量,但是却无法找到该变量的定义。但是有个问题就是242这个按键消息确实传递到了虚拟机内部,只不过显示为0xFF,说明应该是支持的,说明有遗漏。 但是在qemu5.0.0版本中,242按键消息是没有发给虚拟机的,需要进行适配,需要修改qemu-5.0.0/ui/keycodemapdb/data/keymaps.csv、qemu-5.0.0/qapi/ui.json文件。 ui.json:是按键宏定义的配置文件,qemu编译时,根据里面配置生成qapi-util-ui.c文件,该文件定义了所有按键的宏定义,比如:上面截图的Q_KEY_CODE_XXX。 keymaps.csv:各种按键的转换表,qemu编译时,根据这边的内容生成input-keymap-qnum-to-qcode.c、input-keymap-qcode-to-atset2.c等源码,生成qcode、atset等按键表。
11-2、input-keymap-qnum-to-qcode.c文件内容生成
keymaps.csv文件中对于242并没有填写atset1和atset2信息,导致并没有生成到input-keymap-qnum-to-qcode.c文件中去。 该文件通过qemu-5.0.0/ui/keycodemapdb/keymap-gen脚本生成,命令如下:
python3 keymap-gen --lang=glib2 --varname=qemu_inpu_map_atset1_to_qcode code-map data/keymaps.csv atset1 qcode
可以修改最后两个参数变量qemu_input_map_qnum_to_qcode 的索引就是其按键的scancode转换而来的,
索引小于0x80的,scancsode = 索引值,比如:[0x1] = Q_KEY_CODE_ESC,即esc按键的scancode为0x1
索引大于0x80的,scancsode = REDKEY_ESCAPE_BASE +(索引值 - 0x80),比如:[0xec] = Q_KEY_CODE_MAIL,即mail按键的scancode为REDKEY_ESCAPE_BASE + (0xec - 0x80)
添加atset1和atset2信息后就能把该按键信息生成了,但问题在于242按键的atset1和atset2信息是多少?
11-3、AT set1 keycode和AT set2 keycode
在 Linux 和其他操作系统中,AT(AT Keyboard)键盘的按键码(keycode)用于表示键盘上的特定按键。AT set1 和 AT set2 是两种不同的按键码集,通常用于不同的键盘布局或设备。
AT set1 是最早的 AT 键盘按键码集,通常用于标准的 PC 键盘。 按键码的范围通常是从 1 到 255,具体的按键码与按键的功能一一对应。
11-4、atset1和atset2表
atset2 keycodes:https://www.libvirt.org/manpages/https://blog.weghos.com/qemu/qemu/build/ui/input-keymap-atset1-to-qcode.c.html 发现atset1和atset2信息表最大值只到了0xe07d值。
发现github和gitlab仓库显示内容略有不同(github打不开keycodemapdb,但是gitlab可以) https://gitlab.com/qemu-project/qemu/-/tree/stable-8.0/ui?ref_type=headshttps://github.com/qemu/qemu/tree/stable-8.0/ui
开源qemu最终版: https://gitlab.com/qemu-project/keycodemapdb
github单独仓库: https://github.com/qemu/keycodemapdb/blob/master/data/keymaps.csv
最终适配方案:
KEY_VIDEO_NEXT,241,,,0xe071,0x19,,,VK_HANJA,0x19,,,,,,I249,hanja,,
KEY_VIDEO_PREV,242,,,0xe072,0x39,,,VK_HANGEUL,0x15,,,,,,I250,hangeul,,
qemu-5.0.0/ui/spice-input.c 修改kbd_push_key函数对于0xf1和0xf2按键(即>0x80)其keycode = scancode。
11-5、适配依据
至少atset1和atset2缺一不可,这也是最难获取到的值,其中VK_HANJA,0x19和VK_HANGEUL,0x15可以通过rawinputtool.exe程序获取,使用spy++不行,使用KeyboardTest.exe软件获取到了0x19,另外一个值是0xE5,有点差距。
对于atset1值,我们可以通过在qemu2.5.1版本(正常传递242和241按键)增加调试日志发现其qcode分别是113(0x71)和114(0x72),另外我们在https://www.libvirt.org/manpages/virkeycode-qnum.html找到其值。 然后就可以推断其扩展键为0xe071和0xe072。 对于atset2值,也是只能通过qemu2.5.1版本中的hw/input/ps2.c文件中的数组去获取,其对应113和114下标的25和57,即0x19和0x39。
/* Table to convert from PC scancodes to raw scancodes. */
static const unsigned char ps2_raw_keycode[128] = {
0, 118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
50, 49, 58, 65, 73, 74, 89, 124, 17, 41, 88, 5, 6, 4, 12, 3,
11, 2, 10, 1, 9, 119, 126, 108, 117, 125, 123, 107, 115, 116, 121, 105,
114, 122, 112, 113, 127, 96, 97, 120, 7, 15, 23, 31, 39, 47, 55, 63,
71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
};
static const unsigned char ps2_raw_keycode_set3[128] = {
0, 8, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85, 102, 13,
21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 17, 28, 27,
35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 92, 26, 34, 33, 42,
50, 49, 58, 65, 73, 74, 89, 126, 25, 41, 20, 7, 15, 23, 31, 39,
47, 2, 63, 71, 79, 118, 95, 108, 117, 125, 132, 107, 115, 116, 124, 105,
114, 122, 112, 113, 127, 96, 97, 86, 94, 15, 23, 31, 39, 47, 55, 63,
71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87, 111,
19, 25, 57, 81, 83, 92, 95, 98, 99, 100, 101, 103, 104, 106, 109, 110
};
keycode是Windows键码 scancode是BIOS键码 发现114刚好等于242-0x80,113刚好等于241-0x80。
qemu_input_map_qnum_to_qcode 的索引就是其按键的scancode转换而来的, 索引小于0x80的,scancsode = 索引值,比如:[0x1] = Q_KEY_CODE_ESC,即esc按键的scancode为0x1 索引大于0x80的,scancsode = REDKEY_ESCAPE_BASE +(索引值 - 0x80),比如:[0xec] = Q_KEY_CODE_MAIL,即mail按键的scancode为REDKEY_ESCAPE_BASE + (0xec - 0x80) REDKEY_ESCAPE_BASE = 0x100 REDKEY_APP_MAIL = REDKEY_ESCAPE_BASE + 0x6c
12、Windows7系统适配xhci主控
'ich9-usb-xhci' => { bus => 0, addr => 27 },
my $pciaddr_xhci = print_pci_addr("ich9-usb-xhci");
push @$cmd, '-device', "nec-usb-xhci,id=xhci${pciaddr_xhci}.0x0";
my $USB_XHCI_INDEX_START = 0;
my $USB_XHCI_INDEX_END = 12;
for ( my $i = $USB_XHCI_INDEX_START ; $i < $USB_XHCI_INDEX_END ; $i++ ) {
my $portid = $i - $USB_XHCI_INDEX_START + 1;
push @$cmd, '-chardev', "spicevmc,id=charredir$i,name=usbredir";
push @$cmd, '-device', "usb-redir,chardev=charredir$i,id=redir$i,bus=xhci.0,port=$portid";
}
$camera_num = 2;
for ( my $j = $camera_num ; $j < $camera_num + 2 ; $j++ ) {
my $portid = 14 + $j;
push @$cmd, '-chardev', "spicevmc,id=charrd$j,name=usbredir";
push @$cmd, '-device', "redir-video,chardev=charrd$j,id=redirrd$j,bus=xhci.0,port=$portid";
}
虚拟机内部就会出现通用串行总线(USB)控制器未安装驱动,之前把intel的usb3.0驱动尝试一遍都没有正确安装。 最终通过360驱动大师解决了驱动问题。
这个没啥用,主要是制作win7镜像:https://gitcode.com/open-source-toolkit/658e1/overview
12-1、intel usb3.0驱动和renesas usb3.0驱动
Intel 和 Renesas 是两家知名的芯片制造商,它们分别提供 USB 3.0 控制器和相应的驱动程序。
Intel USB 3.0 驱动程序通常与 Intel 芯片组的主板和处理器紧密集成,提供良好的兼容性和性能。 Renesas USB 3.0 驱动程序通常用于 Renesas 生产的 USB 3.0 控制器,广泛应用于各种主板和外部 USB 设备。 硬件 ID 通常会包含厂商 ID 和设备 ID,例如 PCI\VEN_8086&DEV_1E31,其中 VEN_8086 表示 Intel(厂商 ID)。 硬件 ID 通常会包含厂商 ID 和设备 ID,例如 PCI\VEN_1033&DEV_0194,其中 VEN_1033 表示 Renesas(厂商 ID)。
12-2、Windows7虚拟机xhci主控assert异常
kvm: hw/usb/hcd-xhci.c:902: xhci_events_update: Assertion `intr->er_full' failed.
一头雾水,然后就去看开源代码,发现qemu2.5源码就是这样写的,然后发现qemu3.0版本已经没有异常的函数了,然后最终找到stable-2.9版本开始删除该函数的,然后找寻删除代码的提交记录:
https://gitlab.com/qemu-project/qemu/-/commit/898248a32915024a4f01ce4f0c3519509fb703cb
xhci: drop ER_FULL_HACK workaround
The nec/renesas driver problems have finally been debugged and root
caused, see commit "7da76e12 xhci: fix event queue IRQ handling".
大体意思是在"7da76e12 xhci: fix event queue IRQ handling"解决了问题,然后该函数就没有存在必要了,因此重点在另外一个提交记录:
https://gitlab.com/qemu-project/qemu/-/commit/7da76e12cc5cc902dda4c168d8d608fd4e61cbc5
根据这个提交记录修改后,测试设备正常挂载到qemu2.5版本的Windows7系统的xhci主控上面。但是仔细分析修改内容弄不懂,放弃纠结。
12-3、驱动签名SHA-2算法
SHA-1和SHA-2(SHA256)
把所有的KB补丁卸载完后,驱动程序报错: Windows无法验证此设备所需的驱动程序的数字签名。最近的硬件或软件更改安装的文件可能未正确签名或已损坏,或者可能是来自未知来源的恶意软件。(代码 52) 此时可以禁用驱动程序签名强制:控制台重启虚拟机,快速按F8键(也可进入安全模式),或者BIOS延迟开机时间 注意开启测试模式不行: BCDEDIT -SET LOADOPTIONS DISABLE_INTEGRITY_CHECKS BCDEDIT -SET TESTSIGNING ON
另外的方法是打Windows补丁:KB3033929即可(主要支持sha256证书https://blog.csdn.net/xcntime/article/details/142711945)。 Windows补丁网站:https://www.catalog.update.microsoft.com/home.aspx 适用于 x64 系统的 Windows 7 Service Pack 1 (KB976932) ,即Windows7升级到sp1版本
12-4、接12-4关闭驱动签名验证后报新的错误
Windows 无法初始化这个硬件的设备驱动程序。(代码 37) 想不通,在客户的win7旗舰版环境本地就存在一个6.2.9200.22453驱动,签名是RioLin Limited,通过dism++软件导出到内部环境安装后就会报上面的错误,将此驱动安装到win10虚拟机就是正常的。
内部win7虚拟机通过驱动人生和360驱动大师安装的版本是2.1.39.0。
最终在这个网站找到了客户的驱动安装包:https://winraid.level1techs.com/t/solve-the-turbo-problem-of-intel-12th-to-14th-cpus-on-win7/99752https://disk.yandex.ru/d/rTMlnh8LKT43Jg
12-5、PCI-Z 设备硬件检测
PCI-Z 是一款专为 Windows 系统设计的硬件检测工具。它能够提供关于 PCI(包括 PCI-E、PCI-X 等)设备的详细信息,帮助用户识别和解决未知硬件的问题。
官网:https://www.pci-z.com 通过此软件发现硬件信息为uPD720200 USB 3.0 Host Controller,1033:019A,发现win10虚拟机和多台服务器就是这个型号,虽然未看客户的,基本确定应该是一样的。
然后win7虚拟机直接把win10虚拟机的xhci主控驱动安装,报错: Windows 无法初始化这个硬件的设备驱动程序。驱动程序可能已损坏或不见了。(代码 39)
12-6、PCI-Z软件驱动搜索下载
谷歌搜索方式:NEC Corporation uPD720200 USB 3.0 Host Controller Microsoft Windows 7 driver https://www.station-drivers.com/index.php?option=com_remository&Itemid=353&func=fileinfo&id=119&lang=en 可用,版本也是2.1.39.0。
CPU-Z:https://www.cpuid.com/softwares/cpu-z.html PCI-Z 中的 Z 代表 "Zero",强调其对 PCI 设备的管理功能。 CPU-Z 中的 Z 没有特定的含义,主要是作为软件名称的一部分,用于区分和标识该工具。
12-7、qm monitor命令
qm monitor $vmid
info usb
info pci
info version
info qtree
12-8、客户的win7驱动安装到win10虚拟机
测试安装正常,然鹅在重启虚拟机后发现虚拟机蓝屏了,蓝屏代码:KERNEL_SECURITY_CHECK_FAILURE (139) 然后虚拟机怎么都起不来,一启动就蓝屏。
进入安全模式,无果,根本没有启动设置选项(https://www.jianshu.com/p/5101eb1cd1a9),只有命令行能使用。 连续三次重启电源的方式也行不通。(如果连续三次引导尝试失败,Windows 10将在第四次尝试时引导到自动修复模式。) F8按键。
最终发现ucx01000.sys驱动文件是罪魁祸首,它的签名只有RioLin Limited,正常来说win10系统是需要微软签名的,但是在命令行窗口使用del命令删除文件后还是蓝屏,发现ucx01000.sys驱动文件又恢复回来了,原来是该文件在注册表中存在。 只能通过PE系统进去虚拟机,然后手动删除ucx01000.sys驱动文件后是能正常启动虚拟机的,而且ucx01000.sys驱动文件是恢复回来的,这时候发现xhci主控是感叹号状态,设备状态是Windows 无法初始化这个硬件的设备驱动程序。(代码 37)找不到对象名。此时xhci主控驱动没法装其他驱动,说是最新的,而且没法卸载。
这时候删除计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Ucx01000注册表后,ucx01000.sys驱动文件不再恢复,解决蓝屏问题,也解决了xhci主控是感叹号状态,成功卸载了win7驱动。但是xhci主控变成了符合 USB xHCI 的主机控制器,并且设备状态还是Windows 无法初始化这个硬件的设备驱动程序。(代码 37)找不到对象名。
最终个人感觉应该需要ucx01000.sys驱动文件(依据是USBXHCI主控服务里面有写DepenOnService为Ucx010000),然后从另外一台虚拟机把ucx01000.sys驱动文件注册表导入进去,并把ucx01000.sys驱动文件放在C:\Windows\System32\drivers目录,问题最终解决。
12-9、解决客户环境能正常加载ucx01000.sys驱动问题
通过查看客户环境的Windows补丁安装情况,发现有windows6.1-kb2685811-x64_191e09df632b70fd4f4b27d4cb9227f7c5a1c98c.msu和windows6.1-kb2685813-x64_22a969bada171678b0936bb320e6a7778e8adc07.msu,其中发现kb2685811内核模式驱动框架安装后xhci驱动就正常了。 对于驱动签名情况后面有时间再研究,暂时没有啥影响。
13、qemu开启asan内存检查
asan内存扫描需要gcc4.9版本后支持,目前debian源服务器的gcc5.4要求libc 2.14以上版本),而我们打包环境的libc是2.13版本,无法通过源服务安装。 解决方案:代码编译gcc到5.4版本,并使用gcc 5.4对qemu、spice server等模块进行编译。 gcc源码下载:http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-5.4.0/
./configure --prefix=/usr/local/gcc-5.4.0 --enable-threads=posix --disable-checking --disable-multilib --enable-languages=c,c++
make -j4
make install
编译需要依赖mpfr-2.4.2、gmp-4.3.2、mpc-0.8.1等等,需要把缺少的东西都需要弄过来,才能编译出需要的gcc版本。
export CXXFLAGS="-g -O0 -Werror-implicit-function-declaration -fsanitize=address -fno-omit-frame-pointer -lasan"
export CFLAGS="-g -O0 -Werror-implicit-function-declaration -fsanitize=address -fno-omit-frame-pointer -lasan"
export LDFLAGS="$LDFLAGS -Werror-implicit-function-declaration -fsanitize=address -fno-omit-frame-pointer -lasan"
ldd qemu-system-x86_64 | grep asan
需要把libasan.so.2文件拷贝到运行环境
虚拟机需要启动脚本运行才能加载生效:
#!/bin/bash
vmid=8521035828450
qm-c stop ${vmid}
rm -f asan/asan.log.*
export ASAN_OPTIONS="halt_on_error=0:disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1:detect_leaks=1:malloc_context_size=15:log_path=/data/local/asan/asan.log"
cmd=$(qm showcmd ${vmid})
cmd1=$(echo $cmd | sed -e 's/password=/password=f837667a/g')
$cmd1
使用jemalloc自带的内存检查功能
14、0x80异常端点解析导致qemu崩溃
#2 0x00007f0bf78cc621 in_assert_fail ()from /1ib/x86_64-linux-gnu/libc.so.6
#3 0x000055ce4af58361 in usb handTe_packet (dev=0x7fobe7bf4800,p=0x7f0be05f7030) at hw/usb/core.c:410
#4 0x000055ce4af763aa in xhci fire ctl transfer (xhci=0x7fob55000000,xfer=0x7f0be05f7028) at hw/usb/hcd-xhci.c:1969
#5 0x000055ce4af7700a in xhci_kick_ep(xhci=0x7f0b55000000,slotid=1,epid=1, streamid=0) at hw/usb/hcd-xhci.c:2210
#6 0x000055ce4af7a10f in xhci_doorbell_write (ptr=0x7f0b55000000,reg=1,val=1, size=4) at hw/usb/hcd-xhci.c :3384
#7 0x000055ce4ac9aa49 in memory_region_write_accessor (mr=0x7f0b55000d50,addr=4,value=0x7f0be0ffe8e8,size=4,shift=0,mask=4294967295,attrs=...) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:451
#8 0x000055ce4ac9ac5b in access_with_adjusted_size (addr=4,value=0x7f0be0ffe8e8,size=4,access_size_min=1,access_size_max=4,access=0x55ce4ac9a9bf {<}memory_region_write_accessor{>} mr=0x7f0b55000d50, attrs=...) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:507
#9 0x000055ce4ac9dd05 in memory_region_dispatch_write(mr=0x7f0b55000d50,addr=4,data=1,size=4, attrs=...) at /home/vtcompile/buildenv/Trunk/source/app/qemu/qemu-2.5.1/memory.c:1159
#10 0x000055ce4ac3ec57 in address_space_rw(as=0x55ce4b7d5200,addr=4227948548,attrs=...,buf=0x7f0bfe6e1028{<}Address 0x7f0bfe6e1028 out of bounds{>},len=4, is_write=true)
for (i = 0; i < ifp->bNumEndpoints; i++) {
struct libusb_endpoint_descriptor ep;
usbi_parse_descriptor(buffer, "bbbbwb", &ep, host_endian);
if (ep.bDescriptorType == LIBUSB_DT_ENDPOINT && (ep.bEndpointAddress & 0x0F) == 0) {
usbi_warn(ctx, "an invalid endpoint with address 0x%02X, skipping(%d)",
ep.bEndpointAddress, ep.bLength);
buffer += ep.bLength;
parsed += ep.bLength;
size -= ep.bLength;
ifp->bNumEndpoints = (uint8_t)i;
break;
}
}
15、usb1.0协议设备可以挂载xhci主控
只有ehci主控才需要注意uhci主控的位置,否则报错Warning: speed mismatch trying to attach。
16、ubuntu安装qemu
apt install qemu-system