下載後先解壓縮
編譯
第一次make有噴error: include/linux/compiler-gcc.h:106:30: fatal error: linux/compiler-gcc5.h:No such file or directory
把當前kernel原碼中的gcc複製到要編譯的kernel裡即可
https://blog.csdn.net/u014525494/article/details/53573298
$ make -jn #指定用n個核心跑
Enable the kernel for boot(雖然好像他會自己加)
然後直接用這個kernel開機的話會卡在
Uncompressing Linux… done, booting the kernel.
上網查了之後發現可能的原因一大堆,索性直接換了3.10.1看看結果還是一樣
另外發現在make install和update-initramfs的時候有噴amd64-microcode unsupported kernel version
最後改成4.4.1(本來的kernel是4.4.0)就好了
開機之後確定使用的kernel是我們剛剛編的
成功!
最後寫一個程式測試一下
有看到hello world就代表成功了
reference:
https://chybeta.github.io/2017/10/19/Linux-kernel-development-1-环境准备/
https://www.linux.com/tutorials/how-compile-linux-kernel-0/
https://wenyuangg.github.io/posts/linux/linux-add-system-call.html
https://blog.kaibro.tw/2016/11/07/Linux-Kernel編譯-Ubuntu/?fbclid=IwAR0n1xjghssrijlA7L8nFhjojsu-Wdb8w25900l_WVtvDQeJgJzv7MaXxIU
Add a new system call void linux_survey_TT(char *) to your Linux kernel so that you can call it in your program
進程主要由以下幾部分組成:
而其中mm_struct長這樣
其中有關virtual address intervals的資訊:
這些變數存的是process memory layout中個區塊的起始, 結束位址,例如text segments就是start_code ~ end_code
mmap紀錄進程使用到的VMA們
其中vm_area_struct中比較重要的資料有
我猜所有VMA的位址就是process的virtual address intervals了吧!
這邊在做的事就是把每一個vma的vm_start和vm_end搬到result,搬過去之後再將result往後length(sizeof(unsigned long))個bytes
由於我是每個兩個address為一組,所以最後state_end我也保留兩個變數的位址
理解linux paging就很好找了
透過pgd_offset, pud_offset, pmd_offset, pte_offset_kernel取得page table,再配合PAGE_MASK取得physical address,其中並不是每個virtual address都有分配到physical address,所以可能在取某級分頁時發生取不到的狀況,此時我們就可以判斷該virtual address沒有被分配到
由於virtual address對到physical address是以page為單位,也就是說一塊page的起始位置有對應的physical address則代表整塊page都有,所以我就遞迴過每個vma中的每個page
這邊我是以每四個資料為一組(page頭尾+frame頭尾),雖然這麼多result+=length有點醜不過我懶得用其他方法了
我這邊測試端按照剛剛的儲存方式抓出,
其中我這邊把result+=length替換成next,還是沒多好看qq
這裡就有點麻煩了,由於fork()後變成兩個process不能直接把資料存到變數中(child存的parent看不到),所以必須要再讀檔案來分析,超麻煩啊啊啊啊啊啊啊
完成拉~~其實麻煩的都在資料處理qq
reference:
http://gityuan.com/2017/07/30/linux-process/
https://www.cnblogs.com/kwingmei/p/3731746.html
https://www.cnblogs.com/Rofael/archive/2013/04/13/3019153.html
https://www.ffutop.com/posts/2019-07-17-understand-kernel-13/
https://stackoverflow.com/questions/5748492/is-there-any-api-for-determining-the-physical-address-from-virtual-address-in-li
http://www.jollen.org/blog/2007/01/process_vma.html
轉出的physical address有的會超大(前面多一串80000000),於是我把pgd_val, pud_val, pmd_val, pte_val都印出來發現只有pte_val前面多這一串,於是就先去看了下pte_offset_kernel
發現很正常,和上幾層做的事都一樣,那看看pte_val
而當我去找native_pte_val時發現下面有一個function pte_flags,他從native_pte_val中取出flag的部分,也難怪直接取pte_val會包含flag,我們需要把flag去掉,所以先去看看PTE_FLAGS_MASK
看到這邊就恍然大悟,因為我們最後取物理位址是用
但是看看上面幾層在取得物理位址時都是用
其中pxx_page_vaddr都是在做
我們將pte_val的flag濾掉就好了~
page frame number
struct mm_struct 中有一個稱為 mmap 的 field,mmap 的 data type 為 struct vm_area_struct
Linux 以 struct vm_area_struct 資料結構來紀錄每一「區塊」的 VMA 資訊
VMA 是 user process 裡一段 virtual address space 區塊,virtual address space 是連續的記憶體空間,當然 VMA 也會是連續的空間。VMA 對 Linux 的主要好處是,可以記憶體的使用更有效率,並且更容易管理 user process address space。
從另一個觀念來看,VMA 可以讓 Linux kernel 以 process 的角度來管理 virtual address space。Process 的 VMA 對映,可以由 /proc/<pid>/maps 檔案查詢
src: http://www.jollen.org/blog/2007/01/linux_virtual_memory_areas_vma.html
Linux中採用了一種通用的四級分頁機制
在這種分頁機制下,一個完整的線性地址被分為五部分:頁全局目錄+頁上級目錄+頁中間目錄+頁表+偏移量
不管系統採用多少級分頁模型,線性地址本質上都是索引+偏移量的形式,甚至你可以將整個線性地址看作N+1個索引的組合,N是系統採用的分頁級數。在四級分頁模型下,線性地址被分為5部分
虛擬地址轉物理 :
其中PGDIR_SHIFT 39 是 pmd(9)+pud(9)+pte(9)+offset(12)
PGD,PUD,PMD,PTE分別都是一個4k的page,PGD,PUD,PMD,PTE是四張table,table的大小都是4k,其中table的entry分別是: pgd_t, pud_t, pmd_t, pte_t,都是unsigned long類型(8個字節),4k(2的12次方)/8字節(2的3次方)= 512個entry(2的9次方)
後面步驟都差不多,最終拿到 pte 中對應欄的線性地址再用
當我們fork()時,會產生一個和父進程完全相同的子進程(除了pid)
如果按傳統的做法,會直接將父進程的數據拷貝到子進程中,拷貝完之後,父進程和子進程之間的數據段和堆棧是相互獨立的
但是,通常子進程都會執行exec()來做自己想要實現的功能。
exec()
exec函數的作用就是:裝載一個新的程序(可執行映像)覆蓋當前進程內存空間中的映像,從而執行不同的任務。
exec系列函數在執行時會直接替換掉當前進程的地址空間。
所以,如果按照傳統做法的話,創建子進程時復製過去的數據是沒用的(因為子進程執行exec(),原有的數據會被清空),既然很多時候複製給子進程的數據是無效的,於是就有了Copy On Write這項技術了
結論:
實測後:(唯一差別是virtual address都一樣,沒有像上面說的都會不同)
linux支持多種CPU架構,比如x86、ppc和arm等,在不同的處理器結構上參數的傳遞方式都不同,例如
所以為了嚴格區別函數參數的存放位置,引入了兩個標記
attribute((regparm(0))):告訴gcc編譯器該函數不需要通過任何寄存器來傳遞參數,參數只是通過堆棧來傳遞。
attribute((regparm(3))):告訴gcc編譯器這個函數可以通過寄存器傳遞多達3個的參數,這3個寄存器依次為EAX、EDX和ECX。更多的參數才通過堆棧傳遞。這樣可以減少一些入棧出棧操作,因此調用比較快。
編譯的時候就會在對應目錄產生built-in.o的檔案
這些檔案便會被包入在image裡面
編譯的時候就會在對應目錄產生kernel/time/test_udelay.ko的檔案
這些檔案便會被放置在/lib/modules/$(uname -r)
http://yi-jyun.blogspot.com/2017/07/linux-kernel-modules.html
busybox 中的 devmem 可直接操作物理地址
https://github.com/brgl/busybox/blob/master/miscutils/devmem.c
其中他使用到 mmap 和 /dev/mem,透過 mmap 將 /dev/mem 的物理地址映射到 user space,我們就可以像操作虛擬地址般讀寫
要實作出 devmem 有三個主要步驟
3.1 devmem_is_allowed
3.1.1 iomem_is_exclusive 遍歷 iomem_resource,檢查物理地址是否為 busy 或 exclusive
對於外設的IO資源,kernel中使用platform device機制來註冊平台設備(platform_device_register)時調用insert_resource將該設備相應的io資源插入到iomem_resource鍊錶中。
如果我要對某外設的IO資源進行保護,防止用戶空間訪問,可以將其resource的flags置位exclusive即可。
https://blog.csdn.net/skyflying2012/article/details/47611399
3.1.2 不允許訪問 ram 地址
結論: 如果有開 CONFIG_STRICT_DEVMEM 會先檢查
關閉的話就沒有限制
Write a new system call get_process_zero_session_group(unsigned int *, int) so that a process can use it to get the global PIDs of all processes that are in the same login session of process 0
在linux中ID主要有以下四種:
為了管理PID linux使用了許多數據結構,直接從原始碼觀察比較難理解,以下我們慢慢將需求加入來分析
每個 task_struct 有個指向 struct pid 的指標,strcut pid 包含 PID
task_struct 中的 pid_link 多加幾項來只到其組長的 struct pid
struct pid 加上幾項來指回組長的 task_struct
假設今天有三個行程A B C在同一個行程組,其中A是行程組組長
在每個可見行程的 namespace 都會給該行程分配一個 PID,所以一個行程可能有多個 PID
numbers[0] 表示 global namespace,numbers[i] 表示第 i 層 namespace,i 越大所在層級越低
其中 nsproxy 存該行程相關 namespace 資訊
reference:
https://www.cnblogs.com/hazir/p/linux_kernel_pid.html
https://carecraft.github.io/basictheory/2017/03/linux-pid-manage/
class