Try   HackMD

Linux 核心專題: 虛擬攝影機裝置

執行人: hungyuhang
專題解說錄影

Reviewed by 'ollieni'

Skipping BTF generation for /home/hungyuhang/linux2024/vcam/vcam.ko due to unavailability of vmlinux

不是很確定這個訊息代表什麼。
根據BPF Type Format (BTF)

BTF (BPF Type Format) is the metadata format which encodes the debug info related to BPF program/map

BTF file 是和 BPF(Berkerly packet filter)相關的訊息,請問這樣的 warning 對你的專案有影響嗎?

hungyuhang
依我目前的理解,如果 BTF generation 被跳過的話,影響應該是 Linux 無法在我的核心模組裡面插入一些 debug message ,而這些 debug message 是當我們在用 eBPF 技術去對核心模組進行 debug 的時候會被用到。

Reviewed by st10740

試著將 vcam 運行久一點再來看結果(使用這個測試來播放單色輸出的影片約十分鐘),從結果發現隨著時間拉長, vcam 在呼叫 __check_object_size() 跟 _copy_from_user() 的時候也開始出現 memory leak 的狀況。

想請問 __check_object_size() 和 _copy_from_user() 之間是否有關連性呢? 因為在你提供的 stack trace 上似乎沒有看到 __check_object_size()。

hungyuhang
在 vcam 程式碼裡面,當把使用者傳入 Framebuffer API 的資料從 user-space 複製到 kernel-sapce 的時候會使用 copy_from_user() 這個函式,以下式這個函式的程式碼:

static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
	if (check_copy_size(to, n, false))
		n = _copy_from_user(to, from, n);
	return n;
}

其中, check_copy_size() 會間接的呼叫到 __check_object_size() ,所以當程式碼呼叫 copy_from_user() 的時候,在 check_copy_size() 回傳 True 的前提下, __check_object_size()_copy_from_user() 就會依序被呼叫到。

任務簡介

改進 vcam 裝置驅動程式,使其可在新版 Linux 核心運作、通過 V4L2 測試,並支援 DMABUF。

vcam 是個針對 Linux 核心開發的虛擬攝影機裝置,全部程式碼僅 1 千 5 百行,從而可理解 V4L2 (video fro Linux APIs, version 2) 的使用和 Linux 多媒體架構。開發一個虛擬的攝影機裝置除了理解 Linux 核心設計外,也有資訊安全的幫助,例如你可以安插相關程式碼,紀錄有哪些應用程式偷偷啟動攝影機,但過程中又不會揭露真正的隱私。

建立背景知識

理解 vcam 的運作原理: 筆記

TODO: 在 Linux v6.8+ 運作

提交 pull request 並針對近期 Linux 核心進行修正

Patch 1

使用 make 命令編譯核心模組,會出現以下錯誤:

$ make
make -C /lib/modules/6.5.0-35-generic/build M=/home/hungyuhang/linux2024/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-35-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
  You are using:           gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
  CC [M]  /home/hungyuhang/linux2024/vcam/module.o
  CC [M]  /home/hungyuhang/linux2024/vcam/control.o
In file included from ./include/linux/linkage.h:7,
                 from ./arch/x86/include/asm/cache.h:5,
                 from ./include/linux/cache.h:6,
                 from ./arch/x86/include/asm/current.h:9,
                 from ./include/linux/sched.h:12,
                 from ./include/linux/ratelimit.h:6,
                 from ./include/linux/dev_printk.h:16,
                 from ./include/linux/device.h:15,
                 from /home/hungyuhang/linux2024/vcam/control.c:3:
/home/hungyuhang/linux2024/vcam/control.c: In function ‘create_control_device’:
./include/linux/export.h:29:22: error: passing argument 1 of ‘class_create’ from incompatible pointer type [-Werror=incompatible-pointer-types]
   29 | #define THIS_MODULE (&__this_module)
      |                     ~^~~~~~~~~~~~~~~
      |                      |
      |                      struct module *
/home/hungyuhang/linux2024/vcam/control.c:267:38: note: in expansion of macro ‘THIS_MODULE’
  267 |     ctldev->dev_class = class_create(THIS_MODULE, dev_name);
      |                                      ^~~~~~~~~~~
In file included from ./include/linux/device.h:31:
./include/linux/device/class.h:230:54: note: expected ‘const char *’ but argument is of type ‘struct module *’
  230 | struct class * __must_check class_create(const char *name);
      |                                          ~~~~~~~~~~~~^~~~
/home/hungyuhang/linux2024/vcam/control.c:267:25: error: too many arguments to function ‘class_create’
  267 |     ctldev->dev_class = class_create(THIS_MODULE, dev_name);
      |                         ^~~~~~~~~~~~
./include/linux/device/class.h:230:29: note: declared here
  230 | struct class * __must_check class_create(const char *name);
      |                             ^~~~~~~~~~~~
cc1: some warnings being treated as errors
make[3]: *** [scripts/Makefile.build:251: /home/hungyuhang/linux2024/vcam/control.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.5.0-35-generic/Makefile:2039: /home/hungyuhang/linux2024/vcam] Error 2
make[1]: *** [Makefile:234: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

觀察錯誤訊息,可以發現錯誤是來自於 class_create() 呼叫。這個函式只需要一個引數,但是程式碼中卻對這個函式傳入了兩個引數。

會出現這樣的錯誤訊息,是因為 Linux Kernel API 在 Linux v6.4 之後的版本更新了對 create_class() 函式的呼叫方式。

更新前的 API 呼叫方式:

class_create(owner, name)

更新後的 API 呼叫方式:

class_create(name)

對於 Linux Kernel API 的詳細更改可以參考 Linux kernel commit 1aaba11

為了因應以上變動,需要在 vacm 專案的 control.c 內加上以下程式碼:

+ #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
+     ctldev->dev_class = class_create(dev_name);
+ #else
      ctldev->dev_class = class_create(THIS_MODULE, dev_name);
+ #endif

當核心版本大於或等於 v6.4 的時候,就會用新版 Kernel API 的方式去呼叫 create_class() ,反之則使用原來的方式去呼叫。

提交紀錄: sysprog21#38

Patch 2

將 Linux kernel 版本更新到 6.8.0 之後,編譯 vcam 核心組件時又出現了新的問題:

make -C /lib/modules/6.8.0-35-generic/build M=/home/hungyuhang/linux2024/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-35-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  CC [M]  /home/hungyuhang/linux2024/vcam/module.o
  CC [M]  /home/hungyuhang/linux2024/vcam/control.o
  CC [M]  /home/hungyuhang/linux2024/vcam/device.o
  CC [M]  /home/hungyuhang/linux2024/vcam/videobuf.o
/home/hungyuhang/linux2024/vcam/videobuf.c: In function ‘vcam_out_videobuf2_setup’:
/home/hungyuhang/linux2024/vcam/videobuf.c:138:6: error: ‘struct vb2_queue’ has no member named ‘min_buffers_needed’
  138 |     q->min_buffers_needed = 2;
      |      ^~
make[3]: *** [scripts/Makefile.build:243: /home/hungyuhang/linux2024/vcam/videobuf.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-35-generic/Makefile:1926: /home/hungyuhang/linux2024/vcam] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

錯誤來自於 struct vb2_queue 結構體沒有 min_buffers_needed 這個成員。
參考 Linux kernel commit 80c2b40 ,可以發現 Linux 核心在 v6.8 之後就把 min_buffers_needed 改名成 min_queued_buffers 。所以只要將成員名稱做修改就好。

排除上述問題之後,重新執行了一次 make ,但編譯時出現另一個問題:

hungyuhang@UX434FQ:~/linux2024/vcam$ make
make -C /lib/modules/6.8.0-35-generic/build M=/home/hungyuhang/linux2024/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-35-generic'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  You are using:           gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
  CC [M]  /home/hungyuhang/linux2024/vcam/module.o
  CC [M]  /home/hungyuhang/linux2024/vcam/control.o
  CC [M]  /home/hungyuhang/linux2024/vcam/device.o
  CC [M]  /home/hungyuhang/linux2024/vcam/videobuf.o
  CC [M]  /home/hungyuhang/linux2024/vcam/fb.o
/home/hungyuhang/linux2024/vcam/fb.c: In function ‘vcamfb_init’:
/home/hungyuhang/linux2024/vcam/fb.c:405:19: error: ‘FBINFO_FLAG_DEFAULT’ undeclared (first use in this function); did you mean ‘FAULT_FLAG_DEFAULT’?
  405 |     info->flags = FBINFO_FLAG_DEFAULT;
      |                   ^~~~~~~~~~~~~~~~~~~
      |                   FAULT_FLAG_DEFAULT
/home/hungyuhang/linux2024/vcam/fb.c:405:19: note: each undeclared identifier is reported only once for each function it appears in
make[3]: *** [scripts/Makefile.build:243: /home/hungyuhang/linux2024/vcam/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-35-generic/Makefile:1926: /home/hungyuhang/linux2024/vcam] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-35-generic'
make: *** [Makefile:14: kmod] Error 2

這次的錯誤是來自於 Liunx 核心在 v6.6 之後就將 FBINFO_FLAG_DEFAULT 巨集給移除了,詳細可以參考 Linux kernel commit 0444fa3

關於如何更改程式碼,我參考了這個範例FBINFO_FLAG_DEFAULT 被移除時所做的更動,這邊的作法是直接不賦予 fb_info.flags 值。但他們能這樣做的前提都是他們在創立 fb_info 的 struct 的時候是使用 framebuffer_alloc() ,這個函式會直接把整個 fb_info 的 struct 裡面各個成員的值給設為預設值。
但是在 vcam 裡面, fb_info 是用 vmalloc() 去分配記憶體位址,所以無法確保 fb_info 裡面的所有欄位被設定為適當的預設值。所以我在這次的 vcam 程式碼更動也使用了 framebuffer_alloc() 函式來初始化 fb_info 的 struct 。

修改之後再重新執行 make ,可以編譯成功,但是在編譯的輸出裡面有一行訊息:

Skipping BTF generation for /home/hungyuhang/linux2024/vcam/vcam.ko due to unavailability of vmlinux

不是很確定這個訊息代表什麼。

提交紀錄:sysprog21#39

如何快速找到核心程式碼在什麼時候被更改?

善用 git log 和 git blame

通過 V4L2 測試

若遇到測試案例失敗的狀況,修正並提交 pull request。
更新相關的文件

先使用 vcam 專案內的 vcam-util 工具程式確認 video 裝置的名稱:

$ sudo ./vcam-util -l
Available virtual V4L2 compatible devices:
1. fb1(640,480,1/1,rgb24) -> /dev/video4

使用以下命令對 vcam 做測試:

$ sudo v4l2-compliance -d /dev/video4 -f

以下節錄自測試的輸出結果,可以看到所有測試都有通過:

...
Total for vcam device /dev/video4: 48, Succeeded: 48, Failed: 0, Warnings: 0

TODO: 修正記憶體操作

使用 kmodleak 找出記憶體操作的缺失並修正

首先照著 kmodleak readme 裡面的指引進行編譯。

編譯完後,執行 kmodleak :

$ sudo ./kmodleak vcam

並且在另一個終端將 vcam 掛載到核心上面:

$ sudo insmod vcam.ko

當想要停止測試時,將 vcam 卸載:

$ sudo rmmod vcam

vcam 被卸載後, kmodleak 就會自動停止,並且產生報告。
以下節錄自 kmodleak 產生的報告:

關於怎麼看報告可以看這篇

$ sudo ./kmodleak vcam
using page size: 4096
Tracing module memory allocs... Unload module (or hit Ctrl-C) to end
module 'vcam' loaded
module 'vcam' unloaded

27 stacks with outstanding allocations:
32768 bytes in 1 allocations from stack
        addr = 0x15fb48 size = 32768
        0 [<ffffffffaf64edf4>] __alloc_pages+0x264
        1 [<ffffffffaf64edf4>] __alloc_pages+0x264
        2 [<ffffffffaf657b18>] allocate_slab+0xa8
        3 [<ffffffffaf657e28>] new_slab+0x38
        4 [<ffffffffaf65ae35>] ___slab_alloc+0x435
        5 [<ffffffffaf65d2c0>] kmalloc_trace+0x310
        6 [<ffffffffafbc5142>] do_register_framebuffer+0x272
        7 [<ffffffffafbc51f1>] register_framebuffer+0x21
        8 [<ffffffffc51024a2>] crypto_ccm_module_init+0x5492
        9 [<ffffffffc5101191>] crypto_ccm_module_init+0x4181
        10 [<ffffffffc50ff307>] crypto_ccm_module_init+0x22f7
        11 [<ffffffffc5251045>] __kstrtabns_nvKmsKapiGetFunctionsTable+0x8a02
        12 [<ffffffffaf2019bb>] do_one_initcall+0x5b
        13 [<ffffffffaf3f4380>] do_init_module+0xc0
        14 [<ffffffffaf3f6281>] load_module+0xba1
...

上面訊息的意思是說,有一個 32768 bytes 的記憶體空間被配置但是卻沒有被釋放。而在這次測試期間,有偵測到 27 個沒有被釋放掉的記憶體配置。

在這些 stack trace 都沒有看到 vcam 的函式,不確定原因。

經過觀察, vcam 大多數的 memory leak 都來自於下面兩種:

  • 載入模組時或是卸載時所配置的記憶體無法被釋放
    • 這些記憶體都是在呼叫例如 video_register_device() 這類 Linux 提供的 driver 函式時所配置的。
  • 在 framebuffer 的 callback function 呼叫 copy_from_user() 的時候會產生一些無法被釋放的記憶體

而且大多數 memory leak 並不是每次都發生,上面那個 32768 bytes 的 memory leak 就是一個例子。

跟 akvcam 做比對
akvcam :

$ sudo ./kmodleak akvcam
using page size: 4096
Tracing module memory allocs... Unload module (or hit Ctrl-C) to end
module 'akvcam' loaded
module 'akvcam' unloaded

44 stacks with outstanding allocations:

vcam :

$ sudo ./kmodleak vcam
using page size: 4096
Tracing module memory allocs... Unload module (or hit Ctrl-C) to end
module 'vcam' loaded
module 'vcam' unloaded

27 stacks with outstanding allocations:

本來想要以 akvcam 作為範本來改善 vcam 在載入以及卸載模組時所產生的 memory leak ,但是發現 akvcam 也有一樣的問題,例如這兩個專案都無法釋放在呼叫 __video_register_device 時所配置的記憶體。

目前這邊遇到的問題,是從 stack trace 只能得知 vcam driver 在呼叫哪個 kernel api call 的時候配置的記憶體最後沒被釋放掉,但是沒有被釋放掉的原因卻難以追蹤。

__video_register_device 為例,我們可以得知核心在建立 video device 的時候配置了一些記憶體,但是當 vcam 呼叫相對應的 video_unregister_device 的時候,這些相對應的記憶體卻沒被釋放掉,推測有可能是下列原因:

  • 設定 video device 的時候有些設定沒有被正確的設定到
  • 建立 / 消滅 video device 的 api call 順序不對
  • 核心的程式碼本來就會造成 memory leak

但這些都只是推測,要實際知道原因的話可能需要花時間去看原始碼,或是有其他方式?

另外也有注意到配置的記憶體位置有分兩種:

32768 bytes in 1 allocations from stack
        addr = 0x8b070 size = 32768
        0 [<ffffffff8284edf4>] __alloc_pages+0x264
        1 [<ffffffff8284edf4>] __alloc_pages+0x264
        2 [<ffffffff82857b18>] allocate_slab+0xa8
        ...
128 bytes in 1 allocations from stack
        addr = 0xffff98be4329d400 size = 128
        0 [<ffffffff8285ceb3>] kmem_cache_alloc+0x253
        1 [<ffffffff8285ceb3>] kmem_cache_alloc+0x253
        2 [<ffffffff829bac55>] __kernfs_new_node+0x65
        3 [<ffffffff829bc4c6>] kernfs_new_node+0x56
        ...

依照這份文件來看,數字小的記憶體位置(例如上面的 0x8b070 )是 user-space virtual memory ,數字大的(例如上面的 0xffff98be4329d400 )是 Kernel-space virtual memory 。
關於 memory mapping 的概念可以參考這篇以及這篇

嘗試追蹤 memory leak 的原因

試著去追蹤其中一個 memory leak ,這邊選擇了呼叫 register_framebuffer() 時所產生的 memory leak :

32768 bytes in 1 allocations from stack
        addr = 0x8b070 size = 32768
        0 [<ffffffff8284edf4>] __alloc_pages+0x264
        1 [<ffffffff8284edf4>] __alloc_pages+0x264
        2 [<ffffffff82857b18>] allocate_slab+0xa8
        3 [<ffffffff82857e28>] new_slab+0x38
        4 [<ffffffff8285ae35>] ___slab_alloc+0x435
        5 [<ffffffff8285d2c0>] kmalloc_trace+0x310
        6 [<ffffffff82dc5142>] do_register_framebuffer+0x272
        7 [<ffffffff82dc51f1>] register_framebuffer+0x21
        8 [<ffffffffc54bc3d9>] rfcomm_tty_driver+0xb2d1
        9 [<ffffffffc54bb0bd>] rfcomm_tty_driver+0x9fb5
        10 [<ffffffffc54b92d7>] rfcomm_tty_driver+0x81cf
        11 [<ffffffffc54c5045>] rfcomm_tty_driver+0x13f3d
        12 [<ffffffff824019bb>] do_one_initcall+0x5b
        13 [<ffffffff825f4380>] do_init_module+0xc0
        14 [<ffffffff825f6281>] load_module+0xba1

以下是推測的程式執行方式:
do_register_framebuffer() 會用 kmalloc_trace()fb_info->pixmap 配置一個記憶體位置,其中 kmalloc_trace() 會使用 slab 來取得記憶體。

從 slab 取得的記憶體應該是在 kernel memory space ,但是這個 memory leak 的 address 卻是 0x8b070 (位於 user-sapce )。

所以說這個 memory leak 的原因會是因為 slab allocator 不小心配置了錯誤的記憶體位置而導致的嗎?

經過觀察,如果配置記憶體的函式(例如 kmalloc() vmalloc() )的後面有接著呼叫 __alloc_pages() 的話,那它 leak 的 memory address 就會是在 user-space 裡面的,反之如果沒有的話,那麼 leak 的 memory address 就會是在 kernel space 裡面。

嘗試改善呼叫 copy_from_user() 時產生的 memory leak

試著將 vcam 運行久一點再來看結果(使用這個測試來播放單色輸出的影片約十分鐘),從結果發現隨著時間拉長, vcam 在呼叫 __check_object_size()_copy_from_user() 的時候也開始出現 memory leak 的狀況。

3072 bytes in 3 allocations from stack
        addr = 0xffff98bebedd8800 size = 1024
        addr = 0xffff98be55403800 size = 1024
        addr = 0xffff98be75e7e400 size = 1024
        0 [<ffffffff8285d212>] kmalloc_trace+0x262
        1 [<ffffffff8285d212>] kmalloc_trace+0x262
        2 [<ffffffff82f7ec97>] free_iova_fast+0x167
        3 [<ffffffff82f7a7d0>] fq_ring_free_locked+0x50
        4 [<ffffffff82f7b30d>] fq_flush_timeout+0x6d
        5 [<ffffffff82601d87>] call_timer_fn+0x27
        6 [<ffffffff82602132>] __run_timers+0x262
        7 [<ffffffff826021fd>] run_timer_softirq+0x1d
        8 [<ffffffff8363fbce>] __do_softirq+0xde
        9 [<ffffffff825090b7>] __irq_exit_rcu+0xd7
        10 [<ffffffff8250941e>] irq_exit_rcu+0xe
        11 [<ffffffff83624882>] sysvec_apic_timer_interrupt+0x92
        12 [<ffffffff83800f4b>] asm_sysvec_apic_timer_interrupt+0x1b
        13 [<ffffffff82c60c17>] _copy_from_user+0x27
        14 [<ffffffffc54bbde3>] rfcomm_tty_driver+0xacdb
        15 [<ffffffff82dcba8f>] fb_write+0x6f
        16 [<ffffffff828e168d>] vfs_write+0xfd
        17 [<ffffffff828e1dd3>] ksys_write+0x73
        18 [<ffffffff828e1e89>] __x64_sys_write+0x19
        19 [<ffffffff824040be>] x64_sys_call+0x7e
        20 [<ffffffff8361dc3f>] do_syscall_64+0x7f
        21 [<ffffffff83800130>] entry_SYSCALL_64_after_hwframe+0x78

為什麼 calling stack 的記憶體位址不是遞減的?

_copy_from_user() 的功能(參考這個網址):就是把資料從 user-space 複製到 kernel space 。

但是為什麼 copy_from_user() 需要去配置記憶體?

從 stack trace 來看,在呼叫 copy_from_user() 之後,會做以下的事情:

  1. APIC(Advanced Programmable Interrupt Controller) 觸發 timer interrupt
  2. DMA 模組會把某些 page frame 釋放掉?也就是 flush 掉(透過 fq_ring_free_locked()
    • fq = flush queue
    • 從 calling stack 的函式名稱來看,這邊應該跟 RCU 有點關係
  3. 而上述「釋放掉」的過程,會把釋放掉的 page frame 的一些東西資訊放到快取(rcache)裡面,方便下次要申請 iova 的時候可以快速取得,所以會在快取裡面去建立記憶體。

但是在 vcam 的使用情境, framebuffer 理論上並沒有用到 DMA ,那為什麼會呼叫到 DMA 相關的東西?猜想:

  1. 在設定 framebuffer 的時候有東西沒有設定好
  2. 或許在呼叫 fb_write 會啟動 dma
  3. 還是呼叫 copy_from_user 的時候會用到?
    • 目前會猜是這個,因為 framebuffer 框架就我查到的資訊跟 DMA 是無關的。
    • reference

有突發奇想把配置 framebuffer 記憶體位置的 vmalloc(size) 換成 kmalloc(size, GFP_KERNEL) 但是 memory leak 依然存在。

找不到 memory leak 的原因,這個部份先暫緩。

改善載入跟卸載模組時產生的 memory leak

參考 HotMercury 以及 jason50123筆記排除記憶體處理的缺失章節,他們使用了 rcu_barrier() 來解決 memory leak 的問題。( rcu_barrier 的介紹: rcubarrier.txt

具體來說,他們在呼叫 kmem_cache_destroy() 後,又再呼叫了一個 rcu_barrier() 來解決記憶體來不及被釋放掉的問題。實際改動可以參考 RoyWFHuang的這個commit

參照上述的更動,我在將 vcam 卸載時會呼叫到的部份函式呼叫之後加上了 rcu_barrier() 。以下是程式碼改動:

// function vcamfb_destroy in fb.c

    info = fb_data->info;
    if (info) {
        unregister_framebuffer(info);
+       rcu_barrier();
        fb_dealloc_cmap(&info->cmap);
        framebuffer_release(info);
    }
// function destroy_vcam_device in device.c

    if (vcam->sub_thr_id)
        kthread_stop(vcam->sub_thr_id);
    vcamfb_destroy(vcam);
+   rcu_barrier();
    mutex_destroy(&vcam->vcam_mutex);
    video_unregister_device(&vcam->vdev);
    v4l2_device_unregister(&vcam->v4l2_dev);
+   rcu_barrier();

    kfree(vcam);
}
// function free_control_device in control.c

static void free_control_device(struct control_device *dev)
{
    size_t i;
    for (i = 0; i < dev->vcam_device_count; i++)
        destroy_vcam_device(dev->vcam_devices[i]);
    kfree(dev->vcam_devices);
    device_destroy(dev->dev_class, dev->dev_number);
    class_destroy(dev->dev_class);
+   rcu_barrier();
    cdev_del(&dev->cdev);
    unregister_chrdev_region(dev->dev_number, 1);
    kfree(dev);
}

使用這個測試來播放單色輸出的影片約五分鐘來測試兩個不同的版本。
以下是兩個不同版本程式碼用 kmodleak 做測量的部份結果:

  • 原版
    ​​​​$ sudo ./kmodleak vcam
    ​​​​using page size: 4096
    ​​​​Tracing module memory allocs... Unload module (or hit Ctrl-C) to end
    ​​​​module 'vcam' loaded
    ​​​​module 'vcam' unloaded
    
    ​​​​31 stacks with outstanding allocations:
    ​​​​...
    
  • 加了 rcu_barrier() 的版本
    ​​​​$ sudo ./kmodleak vcam
    ​​​​using page size: 4096
    ​​​​Tracing module memory allocs... Unload module (or hit Ctrl-C) to end
    ​​​​module 'vcam' loaded
    ​​​​module 'vcam' unloaded
    
    ​​​​10 stacks with outstanding allocations:
    ​​​​...
    

可以看到,加了 rcu_barrier() 的版本的 memory leak 的數量明顯減少了,但為什麼會減少的原因還有待研究。

TODO: 支援 DMABUF

參考 akvcam

兩專案差異:
akvcam 可以把 VB2_DMABUF 加入 struct vb2_queueio_modes 成員(使用 | 運算子)。
vcam 直接把 struct vb2_queueio_modes 成員的值設為 VB2_MMAP | VB2_USERPTR | VB2_READ

先嘗試將 vcam 的 vb2_queue.io_modes 加入 VB2_DMABUF

-   q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+   q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;

然後運行 vcam 核心模組,核心模組可以正常運作。

如何確認 vb2 模組真的有用 DMA ?