Martins3 / Martins3.github.io

:book: Scratchpad about Linux, Compiler and Virtualization
https://martins3.github.io
GNU General Public License v2.0
61 stars 14 forks source link

https://martins3.github.io//qemu/softmmu.html #52

Closed utterances-bot closed 1 year ago

utterances-bot commented 1 year ago

QEMU 的 softmmu 设计 | Deep Dark Fantasy

:book: Scratchpad about Linux, Compiler and Virtualization

https://martins3.github.io//qemu/softmmu.html

wangzhou commented 1 year ago

请教一个问题,“如果一个 page 只要一个写,那么就会将整个 page 关联的 TB 全部清理“,这句话对应的代码在哪里啊?

Martins3 commented 1 year ago

该机制被这个 patch 简化了,之前的时候会记录 bitmap 可以实现只 flush 被写的 TB,现在看来任何写都会让整个 page 的 TB 被 flush 了。

commit 6981f7026af3507f0bac6a16ae3b098fae12beb2
Author: Richard Henderson <richard.henderson@linaro.org>
Date:   Mon Aug 22 16:23:22 2022 -0700

    accel/tcg: Remove PageDesc code_bitmap

    This bitmap is created and discarded immediately.
    We gain nothing by its existence.

    Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
    Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
    Message-Id: <20220822232338.1727934-2-richard.henderson@linaro.org>

看上去是发现 bitmap 机制没啥用。

清理工作在 tb_invalidate_phys_page_range__locked 中完成

wangzhou commented 1 year ago

感谢回复 :) 昨天看晕了,tb_invalidate_phys_page_range__locked这个函数虽然入参是start/end,但是,还是会被比如tb_invalidate_phys_page这样的函数调用。但是,我在tb_invalidate_phys_page_range__locked上打了断点,然后运行一段自修改的代码,还是触发不了相应的断点。

Martins3 commented 1 year ago
#0  tb_invalidate_phys_page_range__locked (pages=pages@entry=0x7fff8c0975b0, p=0x7fff8c094400, start=start@entry=955832, end=end@entry=955836, retaddr=retaddr@entry=140735945004283) at ../accel/tcg/tb-maint.c:1090
#1  0x0000555555c0ca68 in tb_invalidate_phys_page_fast__locked (ra=140735945004283, len=<optimized out>, start=955832, pages=0x7fff8c0975b0) at ../accel/tcg/tb-maint.c:1209
#2  tb_invalidate_phys_range_fast (ram_addr=955832, size=<optimized out>, retaddr=140735945004283) at ../accel/tcg/tb-maint.c:1224
#3  0x0000555555c1768f in notdirty_write (cpu=cpu@entry=0x55555695cbd0, mem_vaddr=mem_vaddr@entry=955832, size=size@entry=4, retaddr=retaddr@entry=140735945004283, full=<optimized out>, full=<optimized out>) at ../accel/tcg/cputlb.c:1502
#4  0x0000555555c1893c in store_helper (op=MO_32, retaddr=140735945004283, oi=<optimized out>, val=<optimized out>, addr=955832, env=0x55555695d970) at ../accel/tcg/cputlb.c:2375
#5  full_le_stl_mmu (env=0x55555695d970, addr=955832, val=28452, oi=<optimized out>, retaddr=140735945004283) at ../accel/tcg/cputlb.c:2451
#6  0x00007fffa40254fb in code_gen_buffer ()
#7  0x0000555555c09e6a in cpu_tb_exec (cpu=cpu@entry=0x55555695cbd0, itb=itb@entry=0x7fffa40253c0 <code_gen_buffer+152467>, tb_exit=tb_exit@entry=0x7fffa37fd708) at ../accel/tcg/cpu-exec.c:438
#8  0x0000555555c0ab20 in cpu_loop_exec_tb (tb_exit=0x7fffa37fd708, last_tb=<synthetic pointer>, pc=871098, tb=0x7fffa40253c0 <code_gen_buffer+152467>, cpu=0x55555695cbd0) at ../accel/tcg/cpu-exec.c:868
#9  cpu_exec (cpu=cpu@entry=0x55555695cbd0) at ../accel/tcg/cpu-exec.c:1032
#10 0x0000555555c2c560 in tcg_cpus_exec (cpu=cpu@entry=0x55555695cbd0) at ../accel/tcg/tcg-accel-ops.c:69
#11 0x0000555555c2ce27 in rr_cpu_thread_fn (arg=arg@entry=0x55555695cbd0) at ../accel/tcg/tcg-accel-ops-rr.c:223
#12 0x0000555555da0349 in qemu_thread_start (args=<optimized out>) at ../util/qemu-thread-posix.c:505
#13 0x00007ffff6888e86 in start_thread () from /nix/store/9xfad3b5z4y00mzmk2wnn4900q0qmxns-glibc-2.35-224/lib/libc.so.6
#14 0x00007ffff690fd70 in clone3 () from /nix/store/9xfad3b5z4y00mzmk2wnn4900q0qmxns-glibc-2.35-224/lib/libc.so.6

这个很容易触发啊,这是我启动 Linux 内核的时候,很容易就触发了。

wangzhou commented 1 year ago

但是,你看tb_invalidate_phys_range_fast的入参是955832, 这样一路调用下去,感觉并不是把一个page对应tb都删去,而至删去一个range对应的tb。我在跟踪的时候,也看到内核启动会有这样的调用,我是gdb跳过了很多这个断点,等系统起来之后,再跑一个简单smc代码,看看能不能触发断点。

Martins3 commented 1 year ago

过年放假,现在终于有时间重新看这个问题,不好意思哈。

Martins3 commented 1 year ago

tb_invalidate_phys_page_range__locked 中使用 PAGE_FOR_EACH_TB 来遍历所有 tb

#define PAGE_FOR_EACH_TB(start, end, pagedesc, tb, n) \
    TB_FOR_EACH_TAGGED((pagedesc)->first_tb, tb, n, page_next)

实际上,这个宏没有使用参数 start 和 end,而是使用 pagedesc 的。 在 tb_invalidate_phys_page_fast__locked 中:

    p = page_find(start >> TARGET_PAGE_BITS);

也就是根据 start 找到对应的 page 。所以之后的行为是将该 page 关联的 tb 全部清理掉。

wangzhou commented 1 year ago

我看的是7.1.50的qemu, tb_invalidate_phys_page_fast__locked中虽然用PAGE_FOR_EACH_TB遍历了PageDesc中的每个tb, 但是只有tb在start和end的范围之内才会做tb_phys_invalidate__locked这个操作,这样就不是刷掉PageDesc对应的所有tb了,不知道我是不是理解错了。

Martins3 commented 1 year ago

你说的没问题,看来是我一直搞错了,等我有空余的时间会重写下这个 blog。

wangzhou commented 1 year ago

问题是如果这样,那会导致一个基础的问题。qemu里chained tb对于自修改代码会出问题:当自修改代码改动tb对应的代码时,tb里翻译过的代码已经不对了,解决办法是:如果两个chained tb强制在一个PageDesc上,并且一旦有自修改代码,就刷掉PageDesc上的所有tb,那么正确性还是可以保证。所以,也有可能是我们还没有理解对。

Martins3 commented 1 year ago

但是 do_tb_phys_invalidate 处理 chain tb,跳转过来的和从该 tb 指向的 tb 都会做处理

    /* remove the TB from the hash list */
    tb_jmp_cache_inval_tb(tb);

    /* suppress this TB from the two jump lists */
    tb_remove_from_jmp_list(tb, 0);
    tb_remove_from_jmp_list(tb, 1);

    /* suppress any remaining jumps to this TB */
    tb_jmp_unlink(tb);
wangzhou commented 1 year ago

有道理,我顺着这个看看。多谢啊 :)