交換資料
這邊要說明的是user process 與驅動程式交換資料的方法。
這邊是以character這類的驅動程式做說明。
Device file 裝置檔
device file 又可稱為 special file,
通常位於 /dev 目錄底下。
基本上,一個裝置就要有一個device file,
why device file also call special file?
# ls -l /dev/ttyS0
crw-rw---- 1 root dialout 4, 64 2011-12-30 13:20 /dev/ttyS0
# ls -l README
-rw-r--r-- 1 walter walter 1691 2011-12-30 11:00 README
看到前面多了一個c嗎?很明顯和一般檔案不一樣,所以又稱為special file,
而c指的是字元裝置。
當然還有其他裝置檔,如下:
b : 區塊裝置 (block device)
c or u : 字元裝置 (character device/unbuffered device)
p : 具名管線 或稱為FIFO(name pipe)
建立與刪除裝置檔
底下是使用範例
mknod 裝置檔名 種類 主編號 副編號
# mknod /dev/sample c 254 0
# ls -l /dev/sample
crw-r--r-- 1 root root 254, 0 2011-12-30 14:35 /dev/sample
要刪除的話,用rm指令即可
# rm /dev/sample
目前Linux 的major number是12-bit, minor是 20-bit,
合起來一共為 32-bit。
而major/minor number 可以使用的bit數是定義在 include/linux/kdev_t.h
Major Number的豋記方法
如果要將裝置檔和驅動程式連結起來的話,
驅動程式就必須向kernel豋記 major number才行,
目前都採動態豋記為主,所以我們就以動態豋記來做說明。
動態豋記法
Linux 2.6開始建議採用動態豋記法,也就是用cdev結構豋記 major number的方法。
1. 以 alloc_chrdev_region()動態取得 major number。
2. 以 cdev_init() 豋記系統呼叫 handler。
3. 以 cdev_add() 向 kernel豋記驅動程式。
在卸載驅動程式的時候,必須以相反步驟解除豋記才行。
1. 以 cdev_del() 向 kernel釋放驅動程式。
2. 以 unregister_chrdev_region()釋放 major number。
我們就分別來看一下這些函式。
int alloc_chrdev_region(dev_t *dev,
unsigned int firstminor,
unsigned int count,
char *name);
函式如果成呼叫會回傳0, 失敗則會回傳負數。
dev引數就會放進新的 major number及minor number。
firstminor 則是minor number的第一個數字編號。
count 是 minor number的個數,也就是裝置的個數。
name 是驅動程式的名稱。
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev_init負責初使化 cdev結構,並豋記系統呼叫 handler(fops)。
因為 cdev結構變數,在卸載驅動程式的時候還會用到,所以要將它定義為全域變數。
int cdev_add(struct cdev *p, dev_t dev, unsigned int count);
cdev_add()會向 kernel豋記 cdev_init()設定好的裝置資訊,
p 引數可傳入 cdev_init()初使化完成的 cdev結構變數。
dev 則是豋記好的 major number 及 minor number 的起點。
count 則是 minor的個數。
同樣的,該函式如果成呼叫會回傳0, 失敗則會回傳負數。
void cdev_del(struct cdev *p);
會從 kernel釋放驅動程式。
void unregister_chrdev_alloc(dev_t from, unsigned int count);
則會釋放之前拿到的 major number。
from 是 major number 及 minor number 的起點。
count則是當初配置的minor number 個數。
OK, 既然稍作說明過後,我們就來run一下程式碼,才不會如此沉悶吧。
test_alloc_chrdev.c 程式碼如下:
/*****************************************************************************/
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/fs.h>
4 #include <linux/cdev.h>
5 #define DRIVER_NAME "test_chrdev"
6 static unsigned int test_chrdev_alloc_major = 0;
7 static unsigned int num_of_dev = 1;
8 static struct cdev test_chrdev_alloc_cdev;
9 static int test_chrdev_alloc_open(struct inode *inode, struct file *filp)
10 {
11 printk(KERN_ALERT "test_chrdev_alloc is open.\n");
12 return 0;
13 }
14 static int test_chrdev_alloc_close(struct inode *inode, struct file *filp)
15 {
16 printk(KERN_ALERT "test_chrdev_alloc is close.\n");
17 return 0;
18 }
19 struct file_operations fops = {
20 .owner = THIS_MODULE,
21 .open = test_chrdev_alloc_open,
22 .release = test_chrdev_alloc_close,
23 };
24 static int test_alloc_chrdev_init(void)
25 {
26 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
27 int alloc_ret = 0;
28 int cdev_ret = 0;
29 alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
30 if (alloc_ret)
31 goto error;
32 test_chrdev_alloc_major = MAJOR(dev);
33 cdev_init(&test_chrdev_alloc_cdev, &fops);
34 cdev_ret = cdev_add(&test_chrdev_alloc_cdev, dev, num_of_dev);
35 if (cdev_ret)
36 goto error;
37
38 printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, test_chrdev_alloc_major);
39 return 0;
40 error:
41 if (cdev_ret == 0)
42 cdev_del(&test_chrdev_alloc_cdev);
43 if (alloc_ret == 0)
44 unregister_chrdev_region(dev, num_of_dev);
45 return -1;
46 }
47 void test_alloc_chrdev_exit(void)
48 {
49 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
50 cdev_del(&test_chrdev_alloc_cdev);
51 unregister_chrdev_region(dev, num_of_dev);
52 printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
53 }
54 module_init(test_alloc_chrdev_init);
55 module_exit(test_alloc_chrdev_exit);
56 MODULE_LICENSE("GPL") ;
57 MODULE_AUTHOR("Wang Chen Shu");
58 MODULE_DESCRIPTION("This is test_alloc_chrdev module.");
/*****************************************************************************/
Makefile 如下:
/*****************************************************************************/
1 KDIR="/opt/linux-source-2.6.38"
2 PWD=$(shell pwd)
3 obj-m := test_alloc_chrdev.o
4 all:
5 $(MAKE) -C ${KDIR} M=${PWD} modules
6 clean:
7 $(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
我們這裡又準備了一個名叫 test_chrdev.c 的測試程式。
/*****************************************************************************/
1 #include <stdio.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #define DEV_FILE_NAME "/dev/test_chrdev"
5 int main(void)
6 {
7 int fd;
8 fd = open(DEV_FILE_NAME, O_RDWR);
9 if (fd == -1)
10 perror("open");
11 sleep(3);
12 if (close(fd) != 0)
13 perror("close");
14 return 0;
15 }
/*****************************************************************************/
有了程式碼,測試程式以及Makefile,當然直接給它build下去囉^^
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_alloc_chrdev modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.mod.o
LD [M] /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
這時就產生了ko檔。
# insmod test_alloc_chrdev.ko
test_chrdev driver(major number 250) installed.
看到了嗎,我們拿到了 250 這個 major number。
但此時我們並沒有 device file裝置檔,還記得怎麼建來嗎? 哈,想一下喔^^
這樣就行了啊。
# mknod /dev/test_chrdev c 250 0
編譯一下 test_chrdev.c
# gcc test_chrdev.c -o test_chrdev
# ./test_chrdev
經過3秒.....
這時候的訊息,我們就從 dmesg去看吧,走~~
# dmesg | tail
.......... // 以上略過 //
test_chrdev_alloc is open.
test_chrdev_alloc is close.
看到kernel 印出的資訊了嗎?
再來就是刪除裝置檔及卸載模組吧^^
# rm /dev/test_chrdev
# rmmod test_alloc_chrdev
test_chrdev driver removed.
這樣就完成了整個流程,包含豋記及反豋記驅動。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
2011年12月30日 星期五
基礎 Linux Device Driver 驅動程式#4 (執行環境 context)
關於執行環境:
由於裝置驅動程式是屬於kernel程式碼的一部份,
且驅動程式在kernel space運作,
kernel的程式有context(執行環境)的觀念,並分為以下兩種:
Process context(一般行程執行環境)
Interrupt context(中斷執行環境)
但這兩種context有其差異性,如下所示:
------------------------------------------------------------------------
| Context | 可否sleep | 可否被preempt(搶先執行) | 處理時間 |
| Process context | 可 | 可 | 可拖長 |
| Interrupt context | 不可 | 不可 | 極力縮短 |
------------------------------------------------------------------------
重點就在於Interrupt context是全系統執行權限最高的,
如果sleep的話,可想而知,會有什麼事發生,
就是沒人能叫醒它,結果就造成系統呼叫死結,導致kernel panic並停止運作。
因此就要確定,在interrupt context 的kernel 函式都不會用到sleep。
基於同樣理由,故不能被preempt。
關於Endian:
Byte 陣列一次讀寫2個bytes 以上的資料時,byte會以何種次序排列,
就是所謂endian的問題。
那當然, 我們可用簡單的程式來測試一下。
test_endian.c 的程式碼如下:
/*****************************************************************************/
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 int main(void)
5 {
6 unsigned char buf[4];
7 int n = 0xdeabbeef;
8 memcpy(buf, &n, sizeof(n));
9 printf("%02x %02x %02x %02x\n",
10 buf[0], buf[1], buf[2], buf[3]
11 );
12 return 0;
13 }
/*****************************************************************************/
# gcc test_endian.c -o test_endian
# ./test_endian
如果執行結果為
ef be ab de <--- Little endian
de ab be ef <--- Big endian
但通常開發驅動程式的時候,不需要應對endian的問題,因為kernel會把endian的部份吸收。
有endian差異的部份主要包含
CPU
Bus(PCI 或 USB)
網路封包
EEPROM等資料內容
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
由於裝置驅動程式是屬於kernel程式碼的一部份,
且驅動程式在kernel space運作,
kernel的程式有context(執行環境)的觀念,並分為以下兩種:
Process context(一般行程執行環境)
Interrupt context(中斷執行環境)
但這兩種context有其差異性,如下所示:
------------------------------------------------------------------------
| Context | 可否sleep | 可否被preempt(搶先執行) | 處理時間 |
| Process context | 可 | 可 | 可拖長 |
| Interrupt context | 不可 | 不可 | 極力縮短 |
------------------------------------------------------------------------
重點就在於Interrupt context是全系統執行權限最高的,
如果sleep的話,可想而知,會有什麼事發生,
就是沒人能叫醒它,結果就造成系統呼叫死結,導致kernel panic並停止運作。
因此就要確定,在interrupt context 的kernel 函式都不會用到sleep。
基於同樣理由,故不能被preempt。
關於Endian:
Byte 陣列一次讀寫2個bytes 以上的資料時,byte會以何種次序排列,
就是所謂endian的問題。
那當然, 我們可用簡單的程式來測試一下。
test_endian.c 的程式碼如下:
/*****************************************************************************/
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 int main(void)
5 {
6 unsigned char buf[4];
7 int n = 0xdeabbeef;
8 memcpy(buf, &n, sizeof(n));
9 printf("%02x %02x %02x %02x\n",
10 buf[0], buf[1], buf[2], buf[3]
11 );
12 return 0;
13 }
/*****************************************************************************/
# gcc test_endian.c -o test_endian
# ./test_endian
如果執行結果為
ef be ab de <--- Little endian
de ab be ef <--- Big endian
但通常開發驅動程式的時候,不需要應對endian的問題,因為kernel會把endian的部份吸收。
有endian差異的部份主要包含
CPU
Bus(PCI 或 USB)
網路封包
EEPROM等資料內容
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
基礎 Linux Device Driver 驅動程式#3 (hello_multifile驅動)
hello_multifile這支驅動程式與hello驅動程式其實沒太多的差別,
只是差在此驅動程式會引用到其他模組的函式如此。
以下是main.c 的程式碼:
/*****************************************************************************/
1 #include <linux/module.h>
2 #include <linux/init.h>
3 extern void sub();
4 static int hello_multifile_init(void)
5 {
6 printk(KERN_ALERT "hello_multifile is loaded.\n");
7 sub();
8 return 0;
9 }
10 static int hello_multifile_exit(void)
11 {
12 printk(KERN_ALERT "hello_multifile is unloaded.\n");
13 }
14 module_init(hello_multifile_init);
15 module_exit(hello_multifile_exit);
16 MODULE_LICENSE("GPL");
17 MODULE_AUTHOR("Wang Chen Shu");
18 MODULE_DESCRIPTION("Sample test.");
/*****************************************************************************/
看到這一行了嗎?
3 extern void sub();
學過C語言的都知道,
這一行是告訴編譯器說 sub() 在其它位置被定義了,
編譯器就會試著去找sub。
以下是sub.c 的程式碼:
/*****************************************************************************/
1 #include <linux/module.h>
2 #include <linux/init.h>
3 void sub(void)
4 {
5 printk(KERN_ALERT "%s: sub() called\n", __func__);
6 }
/*****************************************************************************/
Makefile如下:
/*****************************************************************************/
1 CFILES := main.c sub.c
2 obj-m := hello.o
3 hello-objs := $(CFILES:.c=.o)
4 KDIR="/opt/linux-source-2.6.38/"
5 PWD=$(shell pwd)
6 all:
7 $(MAKE) -C $(KDIR) M=$(PWD) modules
8 clean:
9 $(MAKE) -C $(KDIR) M=$(PWD) clean
10
/*****************************************************************************/
話不多說,就直接來給它編譯下去囉^^
# make
make -C "/opt/linux-source-2.6.38/" M=/opt/test_driver/my_driver/hello_multifile modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/hello_multifile/main.o
/opt/test_driver/my_driver/hello_multifile/main.c:4:1: warning: function declaration isn’t a prototype
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘hello_multifile_exit’:
/opt/test_driver/my_driver/hello_multifile/main.c:17:1: warning: no return statement in function returning non-void
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘__exittest’:
/opt/test_driver/my_driver/hello_multifile/main.c:20:1: warning: return from incompatible pointer type
CC [M] /opt/test_driver/my_driver/hello_multifile/sub.o
LD [M] /opt/test_driver/my_driver/hello_multifile/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/hello_multifile/hello.mod.o
LD [M] /opt/test_driver/my_driver/hello_multifile/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
此時目錄就會多出hello.ko, 如下:
# ls
hello.ko hello.mod.c hello.mod.o hello.o main.c main.o Makefile modules.order Module.symvers README sub.c sub.o
有了模組,接下來當然是載入囉,還用得著多說嗎?
# insmod hello.ko
hello_multifile is loaded.
sub: sub() called
看到了嗎? 它引用了sub。
移除模組也是和hello一樣的做法。
# rmmod hello
hello_multifile is unloaded.
這樣就成功的移除了模組。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
只是差在此驅動程式會引用到其他模組的函式如此。
以下是main.c 的程式碼:
/*****************************************************************************/
1 #include <linux/module.h>
2 #include <linux/init.h>
3 extern void sub();
4 static int hello_multifile_init(void)
5 {
6 printk(KERN_ALERT "hello_multifile is loaded.\n");
7 sub();
8 return 0;
9 }
10 static int hello_multifile_exit(void)
11 {
12 printk(KERN_ALERT "hello_multifile is unloaded.\n");
13 }
14 module_init(hello_multifile_init);
15 module_exit(hello_multifile_exit);
16 MODULE_LICENSE("GPL");
17 MODULE_AUTHOR("Wang Chen Shu");
18 MODULE_DESCRIPTION("Sample test.");
/*****************************************************************************/
看到這一行了嗎?
3 extern void sub();
學過C語言的都知道,
這一行是告訴編譯器說 sub() 在其它位置被定義了,
編譯器就會試著去找sub。
以下是sub.c 的程式碼:
/*****************************************************************************/
1 #include <linux/module.h>
2 #include <linux/init.h>
3 void sub(void)
4 {
5 printk(KERN_ALERT "%s: sub() called\n", __func__);
6 }
/*****************************************************************************/
Makefile如下:
/*****************************************************************************/
1 CFILES := main.c sub.c
2 obj-m := hello.o
3 hello-objs := $(CFILES:.c=.o)
4 KDIR="/opt/linux-source-2.6.38/"
5 PWD=$(shell pwd)
6 all:
7 $(MAKE) -C $(KDIR) M=$(PWD) modules
8 clean:
9 $(MAKE) -C $(KDIR) M=$(PWD) clean
10
/*****************************************************************************/
話不多說,就直接來給它編譯下去囉^^
# make
make -C "/opt/linux-source-2.6.38/" M=/opt/test_driver/my_driver/hello_multifile modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/hello_multifile/main.o
/opt/test_driver/my_driver/hello_multifile/main.c:4:1: warning: function declaration isn’t a prototype
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘hello_multifile_exit’:
/opt/test_driver/my_driver/hello_multifile/main.c:17:1: warning: no return statement in function returning non-void
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘__exittest’:
/opt/test_driver/my_driver/hello_multifile/main.c:20:1: warning: return from incompatible pointer type
CC [M] /opt/test_driver/my_driver/hello_multifile/sub.o
LD [M] /opt/test_driver/my_driver/hello_multifile/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/hello_multifile/hello.mod.o
LD [M] /opt/test_driver/my_driver/hello_multifile/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
此時目錄就會多出hello.ko, 如下:
# ls
hello.ko hello.mod.c hello.mod.o hello.o main.c main.o Makefile modules.order Module.symvers README sub.c sub.o
有了模組,接下來當然是載入囉,還用得著多說嗎?
# insmod hello.ko
hello_multifile is loaded.
sub: sub() called
看到了嗎? 它引用了sub。
移除模組也是和hello一樣的做法。
# rmmod hello
hello_multifile is unloaded.
這樣就成功的移除了模組。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
基礎 Linux Device Driver 驅動程式#2 (hello world驅動)
今天,我們先以hello world 這個簡單的驅動程式作為我們的第一個範例程式。
kernel的module, 例如驅動程式,並沒有像user space的程式一樣,
能夠使用 stdio.h, string.h等這些標頭檔,
所以取而代之的是 linux/module.h 以及 linux/init.h 這兩個標頭檔,
最基本的驅動程式,都一定會有這兩個標頭檔。
廢話不多說,我們就先來示範一下hello world這支驅動程式。
以下是程式碼:
/*****************************************************************************/
1 #include <linux/init.h>
2 #include <linux/module.h>
3 static int hello_init(void)
4 {
5 printk(KERN_ALERT "Hello world init.\n");
6 return 0;
7 }
8 static void hello_exit(void)
9 {
10 printk(KERN_ALERT "Hello world exit.");
11 }
12 module_init(hello_init);
13 module_exit(hello_exit);
14 MODULE_LICENSE("GPL");
15 MODULE_AUTHOR("Wang Chen Shu");
16 MODULE_DESCRIPTION("This is hello world module test.");
/*****************************************************************************/
// 底下這兩行是模組所需的標頭檔 //
1 #include <linux/init.h>
2 #include <linux/module.h>
/*
下面的hello_init函式則是模組在載入時的進入點,
加上static 是要將命名空間限制在檔案內。
*/
3 static int hello_init(void)
4 {
5 printk(KERN_ALERT "Hello world init.\n");
6 return 0;
7 }
// 下面的hello_exit函式則是模組在卸載時的進入點。 //
8 static void hello_exit(void)
9 {
10 printk(KERN_ALERT "Hello world exit.");
11 }
// 透過module_init 巨集指定hello_init //
12 module_init(hello_init);
// 透過module_exit 巨集指定hello_exit //
13 module_exit(hello_exit);
// 下面則是對模組的一些聲明,像授權方式, 作者以及模組的描述 //
14 MODULE_LICENSE("GPL");
15 MODULE_AUTHOR("Wang Chen Shu");
16 MODULE_DESCRIPTION("This is hello world module test.");
至於, printk的部份,可參考include/linux/printk.h, 如下:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
有了,程式碼,當然您也要準備linux 的kernel 原始碼,
因為模組的編譯需要核心原始碼。
我們這裡是用linux 2.6.38的核心做為示範。
當然您得下載您需要的版本,
# uname -r
2.6.38-13-generic-pae
像我的核心就是 2.6.38,所以視您的版本而定。
當然,您也不想為了每次編譯都打一長串的指令,
所以當初核心的開發人員也為各位想到了這點,所以才有makefile這類的script。
所以,我們還要撰寫一個makefile,檔名為Makefile,內容如下:
/*****************************************************************************/
1 KDIR="/opt/linux-source-2.6.38"
2 PWD=$(shell pwd)
3 VV=
4 obj-m := hello.o
5 all:
6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
7 clean:
8 $(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
-C 後面接的是核心原始碼的路徑
M= 後面接的是目前的目錄位置
6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
這裡看到一個變數VV,如果指定為1的話, 就可以在編譯時看到更詳細的建構過程。
我這裡將我下載的核心原始碼放在/opt做為測試用。
有了驅動原始碼和Makefile, 就可以開始進行下一步了。示範如下:
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V= modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/hello/hello.mod.o
LD [M] /opt/test_driver/my_driver/hello/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
接著我做了一個動作:
# vim Makefile
將
3 VV=
改成
3 VV=1
再做一次。
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V=1 modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
test -e include/generated/autoconf.h -a -e include/config/auto.conf || ( \
echo; \
echo " ERROR: Kernel configuration is invalid."; \
echo " include/generated/autoconf.h or include/config/auto.conf are missing.";\
echo " Run 'make oldconfig && make prepare' on kernel src to fix it."; \
echo; \
/bin/false)
mkdir -p /opt/test_driver/my_driver/hello/.tmp_versions ; rm -f /opt/test_driver/my_driver/hello/.tmp_versions/*
make -f scripts/Makefile.build obj=/opt/test_driver/my_driver/hello
gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.o.d -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude -include include/generated/autoconf.h -Iubuntu/include -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -DMODULE -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello)" -D"KBUILD_MODNAME=KBUILD_STR(hello)" -c -o /opt/test_driver/my_driver/hello/.tmp_hello.o /opt/test_driver/my_driver/hello/hello.c
if [ "-pg" = "-pg" ]; then if [ /opt/test_driver/my_driver/hello/hello.o != "scripts/mod/empty.o" ]; then /opt/linux-source-2.6.38/scripts/recordmcount "/opt/test_driver/my_driver/hello/hello.o"; fi; fi;
(cat /dev/null; echo kernel//opt/test_driver/my_driver/hello/hello.ko;) > /opt/test_driver/my_driver/hello/modules.order
make -f /opt/linux-source-2.6.38/scripts/Makefile.modpost
scripts/mod/modpost -m -a -i /opt/linux-source-2.6.38/Module.symvers -I /opt/test_driver/my_driver/hello/Module.symvers -o /opt/test_driver/my_driver/hello/Module.symvers -S -w -s
gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.mod.o.d -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude -include include/generated/autoconf.h -Iubuntu/include -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello.mod)" -D"KBUILD_MODNAME=KBUILD_STR(hello)" -DMODULE -c -o /opt/test_driver/my_driver/hello/hello.mod.o /opt/test_driver/my_driver/hello/hello.mod.c
ld -r -m elf_i386 -T /opt/linux-source-2.6.38/scripts/module-common.lds --build-id -o /opt/test_driver/my_driver/hello/hello.ko /opt/test_driver/my_driver/hello/hello.o /opt/test_driver/my_driver/hello/hello.mod.o
make[1]: Leaving directory `/opt/linux-source-2.6.38'
看到了嗎,修改成V=1,就秀出更詳細的過程了。
此時,該資料夾下就會多一個hello.ko的模組,就可以進行載入了。
# ls
hello.c hello.mod.c hello.o modules.order README
hello.ko hello.mod.o Makefile Module.symvers
# insmod hello.ko
Hello world init.
看到了,這就成功的載入了hello world的模組。
也可以從這邊看到。
# lsmod | grep hello
hello 12448 0
卸載模組也很簡單,只需下模組的名稱即可。
# rmmod hello
Hello world exit.
看到了嗎,成功的卸載了。
如果要做清除動作的話,就執行clean即可。
# ls
hello.c hello.mod.c hello.o modules.order README
hello.ko hello.mod.o Makefile Module.symvers
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/hello/.tmp_versions
CLEAN /opt/test_driver/my_driver/hello/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
hello.c Makefile README
如此,就成功的清除了。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
kernel的module, 例如驅動程式,並沒有像user space的程式一樣,
能夠使用 stdio.h, string.h等這些標頭檔,
所以取而代之的是 linux/module.h 以及 linux/init.h 這兩個標頭檔,
最基本的驅動程式,都一定會有這兩個標頭檔。
廢話不多說,我們就先來示範一下hello world這支驅動程式。
以下是程式碼:
/*****************************************************************************/
1 #include <linux/init.h>
2 #include <linux/module.h>
3 static int hello_init(void)
4 {
5 printk(KERN_ALERT "Hello world init.\n");
6 return 0;
7 }
8 static void hello_exit(void)
9 {
10 printk(KERN_ALERT "Hello world exit.");
11 }
12 module_init(hello_init);
13 module_exit(hello_exit);
14 MODULE_LICENSE("GPL");
15 MODULE_AUTHOR("Wang Chen Shu");
16 MODULE_DESCRIPTION("This is hello world module test.");
/*****************************************************************************/
// 底下這兩行是模組所需的標頭檔 //
1 #include <linux/init.h>
2 #include <linux/module.h>
/*
下面的hello_init函式則是模組在載入時的進入點,
加上static 是要將命名空間限制在檔案內。
*/
3 static int hello_init(void)
4 {
5 printk(KERN_ALERT "Hello world init.\n");
6 return 0;
7 }
// 下面的hello_exit函式則是模組在卸載時的進入點。 //
8 static void hello_exit(void)
9 {
10 printk(KERN_ALERT "Hello world exit.");
11 }
// 透過module_init 巨集指定hello_init //
12 module_init(hello_init);
// 透過module_exit 巨集指定hello_exit //
13 module_exit(hello_exit);
// 下面則是對模組的一些聲明,像授權方式, 作者以及模組的描述 //
14 MODULE_LICENSE("GPL");
15 MODULE_AUTHOR("Wang Chen Shu");
16 MODULE_DESCRIPTION("This is hello world module test.");
至於, printk的部份,可參考include/linux/printk.h, 如下:
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
有了,程式碼,當然您也要準備linux 的kernel 原始碼,
因為模組的編譯需要核心原始碼。
我們這裡是用linux 2.6.38的核心做為示範。
當然您得下載您需要的版本,
# uname -r
2.6.38-13-generic-pae
像我的核心就是 2.6.38,所以視您的版本而定。
當然,您也不想為了每次編譯都打一長串的指令,
所以當初核心的開發人員也為各位想到了這點,所以才有makefile這類的script。
所以,我們還要撰寫一個makefile,檔名為Makefile,內容如下:
/*****************************************************************************/
1 KDIR="/opt/linux-source-2.6.38"
2 PWD=$(shell pwd)
3 VV=
4 obj-m := hello.o
5 all:
6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
7 clean:
8 $(MAKE) -C ${KDIR} M=${PWD} clean
/*****************************************************************************/
-C 後面接的是核心原始碼的路徑
M= 後面接的是目前的目錄位置
6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
這裡看到一個變數VV,如果指定為1的話, 就可以在編譯時看到更詳細的建構過程。
我這裡將我下載的核心原始碼放在/opt做為測試用。
有了驅動原始碼和Makefile, 就可以開始進行下一步了。示範如下:
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V= modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
CC [M] /opt/test_driver/my_driver/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /opt/test_driver/my_driver/hello/hello.mod.o
LD [M] /opt/test_driver/my_driver/hello/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
接著我做了一個動作:
# vim Makefile
將
3 VV=
改成
3 VV=1
再做一次。
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V=1 modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
test -e include/generated/autoconf.h -a -e include/config/auto.conf || ( \
echo; \
echo " ERROR: Kernel configuration is invalid."; \
echo " include/generated/autoconf.h or include/config/auto.conf are missing.";\
echo " Run 'make oldconfig && make prepare' on kernel src to fix it."; \
echo; \
/bin/false)
mkdir -p /opt/test_driver/my_driver/hello/.tmp_versions ; rm -f /opt/test_driver/my_driver/hello/.tmp_versions/*
make -f scripts/Makefile.build obj=/opt/test_driver/my_driver/hello
gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.o.d -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude -include include/generated/autoconf.h -Iubuntu/include -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -DMODULE -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello)" -D"KBUILD_MODNAME=KBUILD_STR(hello)" -c -o /opt/test_driver/my_driver/hello/.tmp_hello.o /opt/test_driver/my_driver/hello/hello.c
if [ "-pg" = "-pg" ]; then if [ /opt/test_driver/my_driver/hello/hello.o != "scripts/mod/empty.o" ]; then /opt/linux-source-2.6.38/scripts/recordmcount "/opt/test_driver/my_driver/hello/hello.o"; fi; fi;
(cat /dev/null; echo kernel//opt/test_driver/my_driver/hello/hello.ko;) > /opt/test_driver/my_driver/hello/modules.order
make -f /opt/linux-source-2.6.38/scripts/Makefile.modpost
scripts/mod/modpost -m -a -i /opt/linux-source-2.6.38/Module.symvers -I /opt/test_driver/my_driver/hello/Module.symvers -o /opt/test_driver/my_driver/hello/Module.symvers -S -w -s
gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.mod.o.d -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude -include include/generated/autoconf.h -Iubuntu/include -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello.mod)" -D"KBUILD_MODNAME=KBUILD_STR(hello)" -DMODULE -c -o /opt/test_driver/my_driver/hello/hello.mod.o /opt/test_driver/my_driver/hello/hello.mod.c
ld -r -m elf_i386 -T /opt/linux-source-2.6.38/scripts/module-common.lds --build-id -o /opt/test_driver/my_driver/hello/hello.ko /opt/test_driver/my_driver/hello/hello.o /opt/test_driver/my_driver/hello/hello.mod.o
make[1]: Leaving directory `/opt/linux-source-2.6.38'
看到了嗎,修改成V=1,就秀出更詳細的過程了。
此時,該資料夾下就會多一個hello.ko的模組,就可以進行載入了。
# ls
hello.c hello.mod.c hello.o modules.order README
hello.ko hello.mod.o Makefile Module.symvers
# insmod hello.ko
Hello world init.
看到了,這就成功的載入了hello world的模組。
也可以從這邊看到。
# lsmod | grep hello
hello 12448 0
卸載模組也很簡單,只需下模組的名稱即可。
# rmmod hello
Hello world exit.
看到了嗎,成功的卸載了。
如果要做清除動作的話,就執行clean即可。
# ls
hello.c hello.mod.c hello.o modules.order README
hello.ko hello.mod.o Makefile Module.symvers
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
CLEAN /opt/test_driver/my_driver/hello/.tmp_versions
CLEAN /opt/test_driver/my_driver/hello/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
hello.c Makefile README
如此,就成功的清除了。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
基礎 Linux Device Driver 驅動程式#1
前言:
在這之前,我必須假設各位已經有Linux基本指令及相關的知識,
以及C語言程式設計的基礎,和Linux程式設計,
因為我們要說明的是Linux驅動程式,而不是Linux操作以及C語言程式設計。
如果要學習Linux及C語言的話,仿間有許多的書籍可以參考,
至於Linux程式設計的話,也許得上網找一下相關的資料,
在這我們這不再為各位講述其相關的知識。
準備工作:
1. 一台開發用電腦及一套Linux作業系統。
2. 一台目標主機,也可能是目標開發板。
3. gcc 或是跨平台的編譯器。
4. 網路設備,switch hub及網路線。
5. RS232的序列線,筆電可用USB-to-RS232的線。
6. 終端機模擬器,例如: minicom。
7. 一套適合您的編輯器,例如: vi, emacs。
8. 連結器,ld (binutils)。
9. make。
ps. 假設您有開發版的話,在開發時期也可利用nfs較為方便。
我目前的環境是:
作業系統:Ubuntu 11.04 核心: 2.6.38-13-generic-pae
gcc版本: gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
ld 版本: GNU ld (GNU Binutils for Ubuntu) 2.21.0.20110327
make版本:GNU Make 3.81
當然在開發前, 您還需要一些套件:
1. Kernel source code
基於 deb: linux-source-2.6.xx
基於 RPM: kernel-2.6.xx
2. Kernel headers
基於 deb: linux-headers-2.6.xx
基於 RPM: kernel-headers-2.6.xx
3. Kernel Headers for development
基於 deb: linux-libc-dev
基於 RPM: kernel-devel-2.6.xx
等環境一切就序,我們就可以開始進入主題了。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
在這之前,我必須假設各位已經有Linux基本指令及相關的知識,
以及C語言程式設計的基礎,和Linux程式設計,
因為我們要說明的是Linux驅動程式,而不是Linux操作以及C語言程式設計。
如果要學習Linux及C語言的話,仿間有許多的書籍可以參考,
至於Linux程式設計的話,也許得上網找一下相關的資料,
在這我們這不再為各位講述其相關的知識。
準備工作:
1. 一台開發用電腦及一套Linux作業系統。
2. 一台目標主機,也可能是目標開發板。
3. gcc 或是跨平台的編譯器。
4. 網路設備,switch hub及網路線。
5. RS232的序列線,筆電可用USB-to-RS232的線。
6. 終端機模擬器,例如: minicom。
7. 一套適合您的編輯器,例如: vi, emacs。
8. 連結器,ld (binutils)。
9. make。
ps. 假設您有開發版的話,在開發時期也可利用nfs較為方便。
我目前的環境是:
作業系統:Ubuntu 11.04 核心: 2.6.38-13-generic-pae
gcc版本: gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
ld 版本: GNU ld (GNU Binutils for Ubuntu) 2.21.0.20110327
make版本:GNU Make 3.81
當然在開發前, 您還需要一些套件:
1. Kernel source code
基於 deb: linux-source-2.6.xx
基於 RPM: kernel-2.6.xx
2. Kernel headers
基於 deb: linux-headers-2.6.xx
基於 RPM: kernel-headers-2.6.xx
3. Kernel Headers for development
基於 deb: linux-libc-dev
基於 RPM: kernel-devel-2.6.xx
等環境一切就序,我們就可以開始進入主題了。
註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。
訂閱:
文章 (Atom)