主頁 > 作業系統 > linux內核開發

linux內核開發

2021-01-29 06:16:47 作業系統

內核編程常常看起來像是黑魔法,而在亞瑟 C 克拉克的眼中,它八成就是了,Linux內核和它的用戶空間是大不相同的:拋開漫不經心,你必須小心翼翼,因為你編程中的一個bug就會影響到整個系統,浮點運算做起來可不容易,堆疊固定而狹小,而你寫的代碼總是異步的,因此你需要想想并發會導致什么,而除了所有這一切之外,Linux內核只是一個很大的、很復雜的C程式,它對每個人開放,任何人都去讀它、學習它并改進它,而你也可以是其中之一,

img

學習內核編程的最簡單的方式也許就是寫個內核模塊:一段可以動態加載進內核的代碼,模塊所能做的事是有限的——例如,他們不能在類似行程描述符這樣的公共資料結構中增減欄位(LCTT譯注:可能會破壞整個內核及系統的功能),但是,在其它方面,他們是成熟的內核級的代碼,可以在需要時隨時編譯進內核(這樣就可以摒棄所有的限制了),完全可以在Linux源代碼樹以外來開發并編譯一個模塊(這并不奇怪,它稱為樹外開發),如果你只是想稍微玩玩,而并不想提交修改以包含到主線內核中去,這樣的方式是很方便的,

在本教程中,我們將開發一個簡單的內核模塊用以創建一個/dev/reverse設備,寫入該設備的字串將以相反字序的方式讀回(“Hello World”讀成“World Hello”),這是一個很受歡迎的程式員面試難題,當你利用自己的能力在內核級別實作這個功能時,可以使你得到一些加分,在開始前,有一句忠告:你的模塊中的一個bug就會導致系統崩潰(雖然可能性不大,但還是有可能的)和資料丟失,在開始前,請確保你已經將重要資料備份,或者,采用一種更好的方式,在虛擬機中進行試驗,

盡可能不要用root身份

默認情況下,/dev/reverse只有root可以使用,因此你只能使用sudo來運行你的測驗程式,要解決該限制,可以創建一個包含以下內容的/lib/udev/rules.d/99-reverse.rules檔案:

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"

別忘了重新插入模塊,讓非root用戶訪問設備節點往往不是一個好主意,但是在開發其間卻是十分有用的,這并不是說以root身份運行二進制測驗檔案也不是個好主意,

模塊的構造

由于大多數的Linux內核模塊是用C寫的(除了底層的特定于體系結構的部分),所以推薦你將你的模塊以單一檔案形式保存(例如,reverse.c),我們已經把完整的源代碼放在GitHub上——這里我們將看其中的一些片段,開始時,我們先要包含一些常見的檔案頭,并用預定義的宏來描述模塊:

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h> MODULE_LICENSE("GPL");MODULE_AUTHOR("Valentine Sinitsyn <[email protected]>");MODULE_DESCRIPTION("In-kernel phrase reverser");

這里一切都直接明了,除了MODULE_LICENSE():它不僅僅是一個標記,內核堅定地支持GPL兼容代碼,因此如果你把許可證設定為其它非GPL兼容的(如,“Proprietary”[專利]),某些特定的內核功能將在你的模塊中不可用,

什么時候不該寫內核模塊

內核編程很有趣,但是在現實專案中寫(尤其是除錯)內核代碼要求特定的技巧,通常來講,在沒有其它方式可以解決你的問題時,你才應該在內核級別解決它,以下情形中,可能你在用戶空間中解決它更好:

  • 你要開發一個USB驅動 —— 請查看libusb,
  • 你要開發一個檔案系統 —— 試試FUSE,
  • 你在擴展Netfilter —— 那么libnetfilter_queue對你有所幫助,

通常,內核里面代碼的性能會更好,但是對于許多專案而言,這點性能丟失并不嚴重,

由于內核編程總是異步的,沒有一個main()函式來讓Linux順序執行你的模塊,取而代之的是,你要為各種事件提供回呼函式,像這個:

static int __init reverse_init(void){    printk(KERN_INFO "reverse device has been registered\n");    return 0;} static void __exit reverse_exit(void){    printk(KERN_INFO "reverse device has been unregistered\n");} module_init(reverse_init);module_exit(reverse_exit);

這里,我們定義的函式被稱為模塊的插入和洗掉,只有第一個的插入函式是必要的,目前,它們只是列印訊息到內核環緩沖區(可以在用戶空間通過dmesg命令訪問);KERN_INFO是日志級別(注意,沒有逗號),__init__exit是屬性 —— 聯結到函式(或者變數)的元資料片,屬性在用戶空間的C代碼中是很罕見的,但是內核中卻很普遍,所有標記為__init的,會在初始化后釋放記憶體以供重用(還記得那條過去內核的那條“Freeing unused kernel memory…[釋放未使用的內核記憶體……]”資訊嗎?),__exit表明,當代碼被靜態構建進內核時,該函式可以安全地優化了,不需要清理收尾,最后,module_init()module_exit()這兩個宏將reverse_init()reverse_exit()函式設定成為我們模塊的生命周期回呼函式,實際的函式名稱并不重要,你可以稱它們為init()exit(),或者start()stop(),你想叫什么就叫什么吧,他們都是靜態宣告,你在外部模塊是看不到的,事實上,內核中的任何函式都是不可見的,除非明確地被匯出,然而,在內核程式員中,給你的函式加上模塊名前綴是約定俗成的,

這些都是些基本概念 - 讓我們來做更多有趣的事情吧,模塊可以接收引數,就像這樣:

# modprobe foo bar=1

modinfo命令顯示了模塊接受的所有引數,而這些也可以在/sys/module//parameters下作為檔案使用,我們的模塊需要一個緩沖區來存盤引數 —— 讓我們把這大小設定為用戶可配置,在MODULE_DESCRIPTION()下添加如下三行:

static unsigned long buffer_size = 8192;module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));MODULE_PARM_DESC(buffer_size, "Internal buffer size");

這兒,我們定義了一個變數來存盤該值,封裝成一個引數,并通過sysfs來讓所有人可讀,這個引數的描述(最后一行)出現在modinfo的輸出中,

由于用戶可以直接設定buffer_size,我們需要在reverse_init()來清除無效取值,你總該檢查來自內核之外的資料 —— 如果你不這么做,你就是將自己置身于內核例外或安全漏洞之中,

static int __init reverse_init(){    if (!buffer_size)        return -1;    printk(KERN_INFO        "reverse device has been registered, buffer size is %lu bytes\n",        buffer_size);    return 0;}

來自模塊初始化函式的非0回傳值意味著模塊執行失敗,

導航

但你開發模塊時,Linux內核就是你所需一切的源頭,然而,它相當大,你可能在查找你所要的內容時會有困難,幸運的是,在龐大的代碼庫面前,有許多工具使這個程序變得簡單,首先,是Cscope —— 在終端中運行的一個比較經典的工具,你所要做的,就是在內核源代碼的頂級目錄中運行make cscope && cscope,Cscope和Vim以及Emacs整合得很好,因此你可以在你最喜愛的編輯器中使用它,

如果基于終端的工具不是你的最愛,那么就訪問http://lxr.free-electrons.com吧,它是一個基于web的內核導航工具,即使它的功能沒有Cscope來得多(例如,你不能方便地找到函式的用法),但它仍然提供了足夠多的快速查詢功能,

現在是時候來編譯模塊了,你需要你正在運行的內核版本頭檔案(linux-headers,或者等同的軟體包)和build-essential(或者類似的包),接下來,該創建一個標準的Makefile模板:

obj-m += reverse.oall:    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

現在,呼叫make來構建你的第一個模塊,如果你輸入的都正確,在當前目錄內會找到reverse.ko檔案,使用sudo insmod reverse.ko插入內核模塊,然后運行如下命令:

$ dmesg | tail -1[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes

恭喜了!然而,目前這一行還只是假象而已 —— 還沒有設備節點呢,讓我們來搞定它,

混雜設備

在Linux中,有一種特殊的字符設備型別,叫做“混雜設備”(或者簡稱為“misc”),它是專為單一接入點的小型設備驅動而設計的,而這正是我們所需要的,所有混雜設備共享同一個主設備號(10),因此一個驅動(drivers/char/misc.c)就可以查看它們所有設備了,而這些設備用次設備號來區分,從其他意義來說,它們只是普通字符設備,

要為該設備注冊一個次設備號(以及一個接入點),你需要宣告struct misc_device,填上所有欄位(注意語法),然后使用指向該結構的指標作為引數來呼叫misc_register(),為此,你也需要包含linux/miscdevice.h頭檔案:

static struct miscdevice reverse_misc_device = {    .minor = MISC_DYNAMIC_MINOR,    .name = "reverse",    .fops = &reverse_fops};static int __init reverse_init(){    ...    misc_register(&reverse_misc_device);    printk(KERN_INFO ...}

這兒,我們為名為“reverse”的設備請求一個第一個可用的(動態的)次設備號;省略號表明我們之前已經見過的省略的代碼,別忘了在模塊卸下后注銷掉該設備,

static void __exit reverse_exit(void){    misc_deregister(&reverse_misc_device);    ...}

‘fops’欄位存盤了一個指標,指向一個file_operations結構(在Linux/fs.h中宣告),而這正是我們模塊的接入點,reverse_fops定義如下:

static struct file_operations reverse_fops = {    .owner = THIS_MODULE,    .open = reverse_open,    ...    .llseek = noop_llseek};

另外,reverse_fops包含了一系列回呼函式(也稱之為方法),當用戶空間代碼打開一個設備,讀寫或者關閉檔案描述符時,就會執行,如果你要忽略這些回呼,可以指定一個明確的回呼函式來替代,這就是為什么我們將llseek設定為noop_llseek(),(顧名思義)它什么都不干,這個默認實作改變了一個檔案指標,而且我們現在并不需要我們的設備可以尋址(這是今天留給你們的家庭作業),

關閉和打開

讓我們來實作該方法,我們將給每個打開的檔案描述符分配一個新的緩沖區,并在它關閉時釋放,這實際上并不安全:如果一個用戶空間應用程式泄漏了描述符(也許是故意的),它就會霸占RAM,并導致系統不可用,在現實世界中,你總得考慮到這些可能性,但在本教程中,這種方法不要緊,

我們需要一個結構函式來描述緩沖區,內核提供了許多常規的資料結構:鏈接串列(雙聯的),哈希表,樹等等之類,不過,緩沖區常常從頭設計,我們將呼叫我們的“struct buffer”:

struct buffer {    char *data, *end, *read_ptr;    unsigned long size;};

data是該緩沖區存盤的一個指向字串的指標,而end指向字串結尾后的第一個位元組,read_ptrread()開始讀取資料的地方,緩沖區的size是為了保證完整性而存盤的 —— 目前,我們還沒有使用該區域,你不能假設使用你結構體的用戶會正確地初始化所有這些東西,所以最好在函式中封裝緩沖區的分配和識訓,它們通常命名為buffer_alloc()buffer_free()

static struct buffer buffer_alloc(unsigned long size) { struct buffer *buf; buf = kzalloc(sizeof(buf), GFP_KERNEL); if (unlikely(!buf)) goto out; ... out: return buf; }

內核記憶體使用kmalloc()來分配,并使用kfree()來釋放;kzalloc()的風格是將記憶體設定為全零,不同于標準的malloc(),它的內核對應部分收到的標志指定了第二個引數中請求的記憶體型別,這里,GFP_KERNEL是說我們需要一個普通的內核記憶體(不是在DMA或高記憶體區中)以及如果需要的話函式可以睡眠(重新調度行程),sizeof(*buf)是一種常見的方式,它用來獲取可通過指標訪問的結構體的大小,

你應該隨時檢查kmalloc()的回傳值:訪問NULL指標將導致內核例外,同時也需要注意unlikely()宏的使用,它(及其相對宏likely())被廣泛用于內核中,用于表明條件幾乎總是真的(或假的),它不會影響到控制流程,但是能幫助現代處理器通過分支預測技術來提升性能,

最后,注意goto陳述句,它們常常為認為是邪惡的,但是,Linux內核(以及一些其它系統軟體)采用它們來實施集中式的函式退出,這樣的結果是減少嵌套深度,使代碼更具可讀性,而且非常像更高級語言中的try-catch區塊,

有了buffer_alloc()buffer_free()openclose方法就變得很簡單了,

static int reverse_open(struct inode *inode, struct file *file){    int err = 0;    file->private_data = https://www.cnblogs.com/manongxianfeng/archive/2021/01/28/buffer_alloc(buffer_size);    ...    return err;}

struct file是一個標準的內核資料結構,用以存盤打開的檔案的資訊,如當前檔案位置(file->f_pos)、標志(file->f_flags),或者打開模式(file->f_mode)等,另外一個欄位file->privatedata用于關聯檔案到一些專有資料,它的型別是void *,而且它在檔案擁有者以外,對內核不透明,我們將一個緩沖區存盤在那里,

如果緩沖區分配失敗,我們通過回傳否定值(-ENOMEM)來為呼叫的用戶空間代碼標明,一個C庫中呼叫的open(2)系統呼叫(如 glibc)將會檢測這個并適當地設定errno

學習如何讀和寫

“read”和“write”方法是真正完成作業的地方,當資料寫入到緩沖區時,我們放棄之前的內容和反向地存盤該欄位,不需要任何臨時存盤,read方法僅僅是從內核緩沖區復制資料到用戶空間,但是如果緩沖區還沒有資料,revers_eread()會做什么呢?在用戶空間中,read()呼叫會在有可用資料前阻塞它,在內核中,你就必須等待,幸運的是,有一項機制用于處理這種情況,就是‘wait queues’,

想法很簡單,如果當前行程需要等待某個事件,它的描述符(struct task_struct存盤‘current’資訊)被放進非可運行(睡眠中)狀態,并添加到一個佇列中,然后schedule()就被呼叫來選擇另一個行程運行,生成事件的代碼通過使用佇列將等待行程放回TASK_RUNNING狀態來喚醒它們,調度程式將在以后在某個地方選擇它們之一,Linux有多種非可運行狀態,最值得注意的是TASK_INTERRUPTIBLE(一個可以通過信號中斷的睡眠)和TASK_KILLABLE(一個可被殺死的睡眠中的行程),所有這些都應該正確處理,并等待佇列為你做這些事,

一個用以存盤讀取等待佇列頭的天然場所就是結構緩沖區,所以從為它添加wait_queue_headt read\queue欄位開始,你也應該包含linux/sched.h頭檔案,可以使用DECLARE_WAITQUEUE()宏來靜態宣告一個等待佇列,在我們的情況下,需要動態初始化,因此添加下面這行到buffer_alloc()

init_waitqueue_head(&buf->read_queue);

我們等待可用資料;或者等待read_ptr != end條件成立,我們也想要讓等待操作可以被中斷(如,通過Ctrl+C),因此,“read”方法應該像這樣開始:

static ssize_t reverse_read(struct file *file, char __user * out,        size_t size, loff_t * off){    struct buffer *buf = file->private_data;    ssize_t result;    while (buf->read_ptr == buf->end) {        if (file->f_flags & O_NONBLOCK) {            result = -EAGAIN;            goto out;        }        if (wait_event_interruptible        (buf->read_queue, buf->read_ptr != buf->end)) {            result = -ERESTARTSYS;            goto out;        }    }...

我們讓它回圈,直到有可用資料,如果沒有則使用wait_event_interruptible()(它是一個宏,不是函式,這就是為什么要通過值的方式給佇列傳遞)來等待,好吧,如果wait_event_interruptible()被中斷,它回傳一個非0值,這個值代表-ERESTARTSYS,這段代碼意味著系統呼叫應該重新啟動,file->f_flags檢查以非阻塞模式打開的檔案數:如果沒有資料,回傳-EAGAIN

我們不能使用if()來替代while(),因為可能有許多行程正等待資料,當write方法喚醒它們時,調度程式以不可預知的方式選擇一個來運行,因此,在這段代碼有機會執行的時候,緩沖區可能再次空出,現在,我們需要將資料從buf->data 復制到用戶空間,copy_to_user()內核函式就干了此事:

    size = min(size, (size_t) (buf->end - buf->read_ptr));    if (copy_to_user(out, buf->read_ptr, size)) {        result = -EFAULT;        goto out;    }

如果用戶空間指標錯誤,那么呼叫可能會失敗;如果發生了此事,我們就回傳-EFAULT,記住,不要相信任何來自內核外的事物!

    buf->read_ptr += size;    result = size;out:    return result;}

為了使資料在任意塊可讀,需要進行簡單運算,該方法回傳讀入的位元組數,或者一個錯誤代碼,

寫方法更簡短,首先,我們檢查緩沖區是否有足夠的空間,然后我們使用copy_from_userspace()函式來獲取資料,再然后read_ptr和結束指標會被重置,并且反轉存盤緩沖區內容:

    buf->end = buf->data + size;    buf->read_ptr = buf->data;    if (buf->end > buf->data)        reverse_phrase(buf->data, buf->end - 1);

這里, reverse_phrase()干了所有吃力的作業,它依賴于reverse_word()函式,該函式相當簡短并且標記為行內,這是另外一個常見的優化;但是,你不能過度使用,因為過多的行內會導致內核映像徒然增大,

最后,我們需要喚醒read_queue中等待資料的行程,就跟先前講過的那樣,wake_up_interruptible()就是用來干此事的:

    wake_up_interruptible(&buf->read_queue);

耶!你現在已經有了一個內核模塊,它至少已經編譯成功了,現在,是時候來測驗了,

除錯內核代碼

或許,內核中最常見的除錯方法就是列印,如果你愿意,你可以使用普通的printk() (假定使用KERN_DEBUG日志等級),然而,那兒還有更好的辦法,如果你正在寫一個設備驅動,這個設備驅動有它自己的“struct device”,可以使用pr_debug()或者dev_dbg():它們支持動態除錯(dyndbg)特性,并可以根據需要啟用或者禁用(請查閱Documentation/dynamic-debug-howto.txt),對于單純的開發訊息,使用pr_devel(),除非設定了DEBUG,否則什么都不會做,要為我們的模塊啟用DEBUG,請添加以下行到Makefile中:

CFLAGS_reverse.o := -DDEBUG

完了之后,使用dmesg來查看pr_debug()pr_devel()生成的除錯資訊, 或者,你可以直接發送除錯資訊到控制臺,要想這么干,你可以設定console_loglevel內核變數為8或者更大的值(echo 8 /proc/sys/kernel/printk),或者在高日志等級,如KERN_ERR,來臨時列印要查詢的除錯資訊,很自然,在發布代碼前,你應該移除這樣的除錯宣告,

注意內核訊息出現在控制臺,不要在Xterm這樣的終端模擬器視窗中去查看;這也是在內核開發時,建議你不在X環境下進行的原因,

驚喜,驚喜!

編譯模塊,然后加載進內核:

$ make$ sudo insmod reverse.ko buffer_size=2048$ lsmodreverse 2419 0$ ls -l /dev/reversecrw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse

一切似乎就位,現在,要測驗模塊是否正常作業,我們將寫一段小程式來翻轉它的第一個命令列引數,main()(再三檢查錯誤)可能看上去像這樣:

int fd = open("/dev/reverse", O_RDWR);write(fd, argv[1], strlen(argv[1]));read(fd, argv[1], strlen(argv[1]));printf("Read: %s\n", argv[1]);

像這樣運行:

$ ./test 'A quick brown fox jumped over the lazy dog'Read: dog lazy the over jumped fox brown quick A

它作業正常!玩得更逗一點:試試傳遞單個單詞或者單個字母的短語,空的字串或者是非英語字串(如果你有這樣的鍵盤布局設定),以及其它任何東西,

現在,讓我們讓事情變得更好玩一點,我們將創建兩個行程,它們共享一個檔案描述符(及其內核緩沖區),其中一個會持續寫入字串到設備,而另一個將讀取這些字串,在下例中,我們使用了fork(2)系統呼叫,而pthreads也很好用,我也省略打開和關閉設備的代碼,并在此檢查代碼錯誤(又來了):

char *phrase = "A quick brown fox jumped over the lazy dog";if (fork())    /* Parent is the writer */    while (1)        write(fd, phrase, len);else    /* child is the reader */    while (1) {        read(fd, buf, len);        printf("Read: %s\n", buf);}

你希望這個程式會輸出什么呢?下面就是在我的筆記本上得到的東西:

Read: dog lazy the over jumped fox brown quick ARead: A kcicq brown fox jumped over the lazy dogRead: A kciuq nworb xor jumped fox brown quick ARead: A kciuq nworb xor jumped fox brown quick A...

這里發生了什么呢?就像舉行了一場比賽,我們認為readwrite是原子操作,或者從頭到尾一次執行一個指令,然而,內核確實無序并發的,隨便就重新調度了reverse_phrase()函式內部某個地方運行著的寫入操作的內核部分,如果在寫入操作結束前就調度了read()操作呢?就會產生資料不完整的狀態,這樣的bug非常難以找到,但是,怎樣來處理這個問題呢?

基本上,我們需要確保在寫方法回傳前沒有read方法能被執行,如果你曾經撰寫過一個多執行緒的應用程式,你可能見過同步原語(鎖),如互斥鎖或者信號,Linux也有這些,但有些細微的差別,內核代碼可以運行行程背景關系(用戶空間代碼的“代表”作業,就像我們使用的方法)和終端背景關系(例如,一個IRQ處理執行緒),如果你已經在行程背景關系中和并且你已經得到了所需的鎖,你只需要簡單地睡眠和重試直到成功為止,在中斷背景關系時你不能處于休眠狀態,因此代碼會在一個回圈中運行直到鎖可用,關聯原語被稱為自旋鎖,但在我們的環境中,一個簡單的互斥鎖 —— 在特定時間內只有唯一一個行程能“占有”的物件 —— 就足夠了,處于性能方面的考慮,現實的代碼可能也會使用讀-寫信號,

鎖總是保護某些資料(在我們的環境中,是一個“struct buffer”實體),而且也常常會把它們嵌入到它們所保護的結構體中,因此,我們添加一個互斥鎖(‘struct mutex lock’)到“struct buffer”中,我們也必須用mutex_init()來初始化互斥鎖;buffer_alloc是用來處理這件事的好地方,使用互斥鎖的代碼也必須包含linux/mutex.h

互斥鎖很像交通信號燈 —— 要是司機不看它和不聽它的,它就沒什么用,因此,在對緩沖區做操作并在操作完成時釋放它之前,我們需要更新reverse_read()reverse_write()來獲取互斥鎖,讓我們來看看read方法 —— write的作業原理相同:

static ssize_t reverse_read(struct file *file, char __user * out,        size_t size, loff_t * off){    struct buffer *buf = file->private_data;    ssize_t result;    if (mutex_lock_interruptible(&buf->lock)) {        result = -ERESTARTSYS;        goto out;}

我們在函式一開始就獲取鎖,mutex_lock_interruptible()要么得到互斥鎖然后回傳,要么讓行程睡眠,直到有可用的互斥鎖,就像前面一樣,_interruptible后綴意味著睡眠可以由信號來中斷,

    while (buf->read_ptr == buf->end) {        mutex_unlock(&buf->lock);        /* ... wait_event_interruptible() here ... */        if (mutex_lock_interruptible(&buf->lock)) {            result = -ERESTARTSYS;            goto out;        }    }

下面是我們的“等待資料”回圈,當獲取互斥鎖時,或者發生稱之為“死鎖”的情境時,不應該讓行程睡眠,因此,如果沒有資料,我們釋放互斥鎖并呼叫wait_event_interruptible(),當它回傳時,我們重新獲取互斥鎖并像往常一樣繼續:

    if (copy_to_user(out, buf->read_ptr, size)) {        result = -EFAULT;        goto out_unlock;    }    ...out_unlock:    mutex_unlock(&buf->lock);out:    return result;

最后,當函式結束,或者在互斥鎖被獲取程序中發生錯誤時,互斥鎖被解鎖,重新編譯模塊(別忘了重新加載),然后再次進行測驗,現在你應該沒發現毀壞的資料了,

接下來是什么?

現在你已經嘗試了一次內核黑客,我們剛剛為你揭開了這個話題的外衣,里面還有更多東西供你探索,我們的第一個模塊有意識地寫得簡單一點,在從中學到的概念在更復雜的環境中也一樣,并發、方法表、注冊回呼函式、使行程睡眠以及喚醒行程,這些都是內核黑客們耳熟能詳的東西,而現在你已經看過了它們的運作,或許某天,你的內核代碼也將被加入到主線Linux源代碼樹中 —— 如果真這樣,請聯系我們!


via: http://www.linuxvoice.com/be-a-kernel-hacker/

譯者:GOLinux disylee 校對:wxy

本文由 LCTT 原創翻譯,Linux中國 榮譽推出

本文由博客一文多發平臺 OpenWrite 發布!

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/253776.html

標籤:其他

上一篇:linux內核編譯

下一篇:linux修改密碼

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more