顯示具有 Linux Driver 標籤的文章。 顯示所有文章
顯示具有 Linux Driver 標籤的文章。 顯示所有文章

2018年10月2日 星期二

編譯Linux Kernel時,版本後面被加了符號,把svn的檔案刪掉的方法

如果你的版本控管是使用svn,Linux Code check out 下來後會有一些svn版本控管的檔案,而這些檔案在你編譯Kernel的時候,會在你的版本號後面多加一些資訊,例如:

- 你使用Kernel 4.14.12版本,並且編譯他,實際上編譯出來的Kernel你使用uname -a 看到的會是4.14.12

- 但若是有伴隨一些svn版本控管的檔案時,編譯出來有可能會是 4.14.12+

以上的差異點,當你編譯module要給別人使用時,別人的Kernel就會報錯,會有如下的訊息:
btn_driver: version magic '4.14.12+ SMP mod_unload ' should be '4.14.12 SMP mod_unload '

======================= 分隔線 =============================
因此要想清除掉svn的檔案,再作編譯Kernel的動作,可以在kernel 根目錄下,用下面的指令:
find . -name '.svn' | xargs rm -rf

清除掉svn檔案之後,再作編譯,就不會有以上的問題發生了~

2017年2月24日 星期五

Linux內核驅動程序初始化順序的調整


今天在做一個驅動的時候要用到另一個驅動(I2C)提供的API,在內核初始化時碰到了一個依賴問題。

我的驅動在I2C初始化之前就運行起來了,而這時I2C提供的API還處於不可用狀態。查了很多資料,網上有人說所有使用module_init這個宏的驅動程序的起動順序都是不確定的(我沒有查到權威的資料)。

所有的__init函數在區段.initcall.init中還保存了一份函數指針,在初始化時內核會通過這些函數指針調用這些__init函數指針,並在整個初始化完成後,釋放整個init區段(包括.init.text,.initcall.init等)。

注意,這些函數在內核初始化過程中的調用順序只和這裡的函數指針的順序有關,和1)中所述的這些函數本身在.init.text區段中的順序無關。在2.4內核中,這些函數指針的順序也是和鏈接的順序有關的,是不確定的。在2.6內核中,initcall.init區段又分成7個子區段,分別是
?
1
2
3
4
5
6
7
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
當需要把函數fn放到.initcall1.init區段時,只要聲明
?
1
core_initcall(fn);
即可。
其他的各個區段的定義方法分別是:
?
1
2
3
4
5
6
7
core_initcall(fn) --->.initcall1.init
postcore_initcall(fn) --->.initcall2.init
arch_initcall(fn) --->.initcall3.init
subsys_initcall(fn) --->.initcall4.init
fs_initcall(fn) --->.initcall5.init
device_initcall(fn) --->.initcall6.init
late_initcall(fn) --->.initcall7.init


而與2.4兼容的initcall(fn)則等價於device_initcall(fn)。各個子區段之間的順序是確定的,即先調用.initcall1.init中的函數指針,再調用.initcall2.init中的函數指針,等等。而在每個子區段中的函數指針的順序是和鏈接順序相關的,是不確定的。

在內核中,不同的init函數被放在不同的子區段中,因此也就決定了它們的調用順序。這樣也就解決了一些init函數之間必須保證一定的調用順序的問題。按照include/Linux/init.h文件所寫的,我在驅動裡償試了這樣兩種方式:

?
1
2
__define_initcall("7", fn);
late_initcall(fn);
都可以把我的驅動調整到最後調用。實際上上面兩個是一回事:
?
1
#define late_initcall(fn) __define_initcall("7", fn)

Busybox的halt, poweroff, reboot指令,以及driver內可以作這些動作的signal

指令 halt、poweroff、跟 reboot 的動作過程類似,都是 kill pid=1 的行程,一般是 init,pid=1 的行程會去執行 inittab 的 shutdown 動作後,呼叫函式庫函式 reboot()。reboot() 再執行系統呼叫進到 kernel 執行 sys_reboot() ,去做相關動作。

Signal的表格如下所示:


signal to kill initreboot() 參數kernel動作
haltSIGUSR1RB_HALT_SYSTEM (0xcdef0123)kernel_halt()停止所有程式
poweroffSIGUSR2RB_POWER_OFF (0x4321fedc)kernel_power_off()停止所有程式後關電 (關電未必有實作)
rebootSIGTERMRB_AUTOBOOT (0x01234567)kernel_restart()停止所有程式後再開機


與busybox的道理相同,假設我在driver內寫一個 kill_cad_pid(SIGTERM, 1);
接下來就會執行reboot()的動作

假設我是寫 kill_cad_pid(SIGUSR2, 1);
則接下來會進入poweroff的狀態

給不同的Signal會有不同的結果!



如果下這三個busybox指令的其中一個,再加上 -f 參數的話,就直接執行 reboot(),不做 inittab 的 shutdown 動作。

2016年7月28日 星期四

如何撰寫Linux Kernel中的Timer機制

想要在kernel中寫Timer的機制,首先要些對一些專有名詞有一些初步的了解,分別是HZ, Tick, Jiffies.

HZ:Linux kernel 每隔固定週期會發出timer interrupt,HZ就是用來定義每一秒有幾次timer interrupts。例如:500 HZ 就是CPU每一秒會發出500個interrupt的意思。這個值是可以在Kernel的menuconfig當中來做設定.

Tick:Tick是HZ的倒數,意即timer interrupt每發生一次中斷所花的時間。如HZ為500時,tick為2毫秒 (millisecond)。

Jiffies:Jiffies為Linux核心變數(32位元變數,unsigned long),它被用來紀錄系統自開幾以來,已經過多少的tick。每發生一次timer interrupt,Jiffies變數會被加一。


接下來開始介紹Timer API的做法,我們先來看以下定義所代表的意義:

首先先宣告一個Timer的Struct:
struct timer_list timer;

這個Struct當中有幾個比較重要的值,我們要去做設定分別是:
timer.function    <===== timer 啟動後所設定的expires時間到後,會去做此參數所指定的function
timer.data             <===== timer 傳入function的參數值
timer.expires      <===== timer 延遲的時間,也就是延遲多久後去做 timer.function 所指定的函式

而下面這部分則是想要timer delay多久的寫法
Jiffies + HZ;   <=====  1秒之後 

Jiffies  + HZ/2;       <=====   半秒之後

Jiffies  + 20*HZ;     <=====  20秒之後


接下來就是實做一個Timer的Code.
/**************** Kernel_Timer *************/
struct timer_list danny_timer;

static int danny_do(void)
{
    danny_timer.expires = jiffies + HZ;
    add_timer(&danny_timer);
}

static void danny_timer_init(void)
{
    /* Timer 初始化 */
   init_timer(&danny_timer);

   /* define timer 要執行之函式 */
  danny_timer.function = danny_do;

  /* define timer 傳入函式之 Data */
  danny_timer.data = ((unsigned long) 0);

  /* define timer Delay 1秒的時間 */
  danny_timer.expires = jiffies + HZ;

  /* 啟動 Timer*/
  add_timer(&danny_timer);
}
/*********************************************/

結論:

所以當呼叫 danny_timer_init();之後,delay 1秒之後,會去做danny_do();

而danny_do()裡面也加了add_timer()的函式,所以會不斷的迴圈,每隔1秒鐘都會去做danny_do()的函式.



 

2016年5月19日 星期四

Linux_Driver入門-寫一個HelloWorld module

因為是driver的初學者,所以先練習寫一支HelloWorld的module,這個module的行為是當insmod這個module的時候,會印出"Hello World!"的字眼,而在rmmod的時候會印出"Goodbye, hello world"

首先描述一下整個編譯環境,以及要掛這個module的平台:
首先說明要掛這個module的平台
- arm64平台(DB410C),系統是linux

編譯平台
- PC,系統是ubuntu,DB410C的kernel與toolchain都準備好了

這邊先說明一下,當你要作交叉編譯時,一定要有Target Board(DB410C)的toolchain,以這邊的例子為例,我在PC上面編譯,我利用toolchain把程式編譯成Target Board可讀的檔案

另外要注意的是,要編譯driver,必須要拿到與Target Board相同的Kernel Source Code(版本要相同),Source code必須整包放在要進行編譯的PC端,編譯時必須參考到裡面的一些header檔

接著就來看我們的HelloWorld.c

///////////////////////////////////////////  HelloWorld.c  ///////////////////////////////////////////
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    printk("Hello world!\n");
    return 0;
}

static void hello_exit(void)
{
    printk("Goodbye, hello world\n");
}

module_init(hello_init);
module_exit(hello_exit);
///////////////////////////////////////////////////////////////////////////////////////////////////////////

這程式很單純,在module被insert的時候就會執行hello_init的function
在module被remove時會執行hello_exit的function



接著是Makefile的部分

obj-m += hello.o

KERNEL=/home/danny/DB410C/kernel/kernel

all:
make -C $(KERNEL) M=$(shell pwd) modules ARCH=arm64

clean:
make -C $(KERNEL) M=$(shell pwd) clean


編譯之前這邊要注意兩個部分,首先是必須給系統CROSS_COMPILE環境變數,必須給他toolchain的完整路徑,請使用export指令:
export CROSS_COMPILE=/home/Danny/DB410C/toolchain/gcc-linaro-4.9-2014.11-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
(請依照你的toolchain給定CROSS_COMPILE的值)

另外一部分是Makefile中KERNEL這個參數,必須要指定到PC上,Target Board(DB410C)的Kernel Source Code,這樣編譯時才不會出錯!


接著只需下make,hello.ko就會被產生出來,接著把hello.ko複製到Target Board上,接著執行
sudo insmod hello.ko

sudo rmmod hello.ko

輸入dmesg就可以看到driver印出的訊息囉!