目的
在上一篇文章 《嵌入式Linux驅(qū)動開發(fā) 01:基礎(chǔ)開發(fā)與使用》 中我們已經(jīng)實現(xiàn)了最基礎(chǔ)的驅(qū)動功能。在那篇文章中我們的驅(qū)動代碼是獨立于內(nèi)核代碼存放的,并且我們的驅(qū)動編譯后也是一個獨立的模塊。在實際使用中將驅(qū)動代碼放在內(nèi)核代碼中,并將驅(qū)動編譯到內(nèi)核中也是比較常見的選擇,這篇文章將此進行介紹。
這篇文章中內(nèi)容均在下面的開發(fā)板上進行測試:
《新唐NUC980使用記錄:自制開發(fā)板(基于NUC980DK61YC)》
這篇文章主要是在下面文章基礎(chǔ)上進行的:
《新唐NUC980使用記錄:訪問以太網(wǎng)(LAN8720A) & 啟用SSH》
基礎(chǔ)說明
將驅(qū)動程序添加到內(nèi)核中可以分為兩層含義來理解:
-
將驅(qū)動程序源碼等放到 Linux Kernel 源碼目錄下
Linux Kernel 源碼中通常將驅(qū)動放到drivers/
下,本文中也將驅(qū)動源碼放到這個目錄下; -
在 Linux Kernel 配置管理工具中統(tǒng)一管理與編譯
這條主要指可以在menuconfig
中進行配置管理來選擇編譯(這里先不討論使用設(shè)備樹的情況);menuconfig
界面中各個菜單和選項都是由Kconfig
文件定義的,所以我們需要修改和編寫相關(guān)文件;
在menuconfig
中配置最終改變的是 make 時Makefile
文件中各個變量,我們的自己的驅(qū)動也需要Makefile
文件來指定編譯規(guī)則,并結(jié)合Kconfig
文件中定義的變量來控制編譯過程;
添加到內(nèi)核中
本文中演示中涉及目錄與文件結(jié)構(gòu)組織如下:
其中 char_dev
就是本文中要添加的驅(qū)動。 user/
目錄用于統(tǒng)一存放自己編寫的驅(qū)動,如果沒有這個需求這一層可以去掉,這樣結(jié)構(gòu)上會更簡單些,當(dāng)然推薦還是留著。各目錄下的 Kconfig
是一層層應(yīng)用的, Makefile
同理。
進入源碼目錄并建立相關(guān)目錄和文件:
cd ~/nuc980-sdk/NUC980-linux-4.4.y/
mkdir -p drivers/user
touch drivers/user/Kconfig
touch drivers/user/Makefile
mkdir -p drivers/user/char_dev
touch drivers/user/char_dev/char_dev.c
touch drivers/user/char_dev/Kconfig
touch drivers/user/char_dev/Makefile
Kconfig
首先修改drivers目錄下Kconfig文件:
gedit drivers/Kconfig
在其中添加下面一行,用來引用drivers/user目錄下的Kconfig文件:
source "drivers/user/Kconfig"
接著編輯drivers/user目錄下的Kconfig文件:
gedit drivers/user/Kconfig
寫入下面內(nèi)容,用來引用drivers/user/char_dev目錄下的Kconfig文件:
menu "User drivers"
source "drivers/user/char_dev/Kconfig"
endmenu
最后編輯drivers/user/char_dev目錄下的Kconfig文件:
gedit drivers/user/char_dev/Kconfig
寫入下面內(nèi)容:
config USER_CHAR_DEV
tristate "char_dev"
default n
help
char_dev driver test.
上面內(nèi)容中 config USER_CHAR_DEV
表示設(shè)置一個可配置的變量,名稱為 USER_CHAR_DEV
(保存后實際的變量名會在頭部添加 CONFIG_
即 CONFIG_USER_CHAR_DEV
,這個變量可以在Makefile中使用)。 tristate
表示該變量可取值為 n/y/m
,后面的字符串為該條目在 menuconfig
中顯示的文本。 default n
表示該變量默認值。 help
表示其下面的內(nèi)容是可在 menuconfig
中查看幫助信息。
經(jīng)過上面處理后就可以在 menuconfig
中看到相關(guān)選項并進行操作了:
Makefile
首先修改drivers目錄下Makefile文件:
gedit drivers/Makefile
在其中添加下面一行,這樣編譯時會進入drivers/user目錄下:
obj-y += user/
接著編輯drivers/user目錄下的Makefile文件:
gedit drivers/user/Makefile
寫入下面內(nèi)容,這樣編譯時會進入drivers/user/char_dev目錄下:
obj-y += char_dev/
最后編輯drivers/user/char_dev目錄下的Makefile文件:
gedit drivers/user/char_dev/Makefile
寫入下面內(nèi)容:
obj-$(CONFIG_USER_CHAR_DEV) += char_dev.o
上面就是最終編譯驅(qū)動程序過程了,這里的 CONFIG_USER_CHAR_DEV
變量就是由前面配置來產(chǎn)生的。根據(jù)變量的值,其最終可能產(chǎn)生 obj-n
、 obj-y
、 obj-m
幾個結(jié)果,這幾個是 Linux Kernel 源碼總的Makefile中定義的變量,添加到 obj-y
的內(nèi)容會編譯到內(nèi)核中,添加到 obj-m
的內(nèi)容會編譯成單獨的模塊。
驅(qū)動程序
最后編輯下進行測試用的驅(qū)動程序:
gedit drivers/user/char_dev/char_dev.c
直接使用上一篇文章的程序即可:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static int major = 0;
static const char *char_dev_name = "char_dev";
static struct class *char_dev_class;
static struct device *char_dev_device;
static char dev_buf[4096];
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static int char_dev_open(struct inode *node, struct file *file)
{
return 0;
}
static int char_dev_close(struct inode *node, struct file *file)
{
return 0;
}
static ssize_t char_dev_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int ret;
ret = copy_to_user(buf, dev_buf, MIN(size, 4096)); // 從內(nèi)核空間拷貝數(shù)據(jù)到用戶空間
return ret;
}
static ssize_t char_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int ret;
ret = copy_from_user(dev_buf, buf, MIN(size, 4096)); // 從用戶空間拷貝數(shù)據(jù)到內(nèi)核空間
return ret;
}
static const struct file_operations char_dev_fops = {
.owner = THIS_MODULE,
.open = char_dev_open,
.release = char_dev_close,
.read = char_dev_read,
.write = char_dev_write,
};
static int __init char_dev_init(void)
{
printk("modlog: func %s, line %d.\n", __FUNCTION__, __LINE__);
major = register_chrdev(0, char_dev_name, &char_dev_fops); // 注冊字符設(shè)備,第一個參數(shù)0表示讓內(nèi)核自動分配主設(shè)備號
char_dev_class = class_create(THIS_MODULE, "char_dev_class"); //
if (IS_ERR(char_dev_class))
{
unregister_chrdev(major, char_dev_name);
return -1;
}
char_dev_device = device_create(char_dev_class, NULL, MKDEV(major, 0), NULL, char_dev_name); // 創(chuàng)建設(shè)備節(jié)點創(chuàng)建設(shè)備節(jié)點,成功后就會出現(xiàn)/dev/char_dev_name的設(shè)備文件
if (IS_ERR(char_dev_device))
{
device_destroy(char_dev_class, MKDEV(major, 0));
unregister_chrdev(major, char_dev_name);
return -1;
}
return 0;
}
static void __exit char_dev_exit(void)
{
printk("modlog: func %s, line %d.\n", __FUNCTION__, __LINE__);
device_destroy(char_dev_class, MKDEV(major, 0)); // 銷毀設(shè)備節(jié)點,銷毀后/dev/下設(shè)備節(jié)點文件就會刪除
class_destroy(char_dev_class);
unregister_chrdev(major, char_dev_name); // 注銷字符設(shè)備
}
module_init(char_dev_init); // 模塊入口
module_exit(char_dev_exit); // 模塊出口
MODULE_LICENSE("GPL"); // 模塊許可
這個驅(qū)動程序在安裝和卸載時打印了一些消息,可以通過此判斷驅(qū)動程序是否工作。
編譯與測試
模塊方式
在 menuconfig
將控制驅(qū)動的選項選擇為模塊后進行編譯:
# make menuconfig
export PATH=$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin
# 可以使用make整體編譯或使用make modules編譯單獨模塊
# make
make modules
最后編譯生成的模塊默認在模塊源碼目錄下,可以拷貝到開發(fā)板中進行測試:
# scp drivers/user/char_dev/char_dev.ko root@192.168.31.142:/root/
編譯到內(nèi)核中
在 menuconfig
將控制驅(qū)動的選項選擇為y后進行編譯:
# make menuconfig
# export PATH=$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin
make uImage
編譯完成后拷貝內(nèi)核文件到開發(fā)板boot分區(qū):
# 在開發(fā)板中掛載啟動分區(qū)
# mount /dev/mmcblk0p1 /mnt/
# 在虛擬機中拷貝編譯生成的內(nèi)核到開發(fā)板
# scp ../image/980uimage root@192.168.31.142:/mnt/
開發(fā)板重啟后可以看到驅(qū)動程序在內(nèi)核啟動時自動啟動了,可以看到打印的信息以及 /dev/ 下的設(shè)備文件。文章來源:http://www.zghlxwxcb.cn/news/detail-408227.html
總結(jié)
將驅(qū)動程序添加到內(nèi)核中還是比較簡單的,按照內(nèi)核源碼本身的組織方式來進行就行了。更多的示例可以參考內(nèi)核源碼 drivers/ 目錄下各個驅(qū)動。文章來源地址http://www.zghlxwxcb.cn/news/detail-408227.html
到了這里,關(guān)于嵌入式Linux驅(qū)動開發(fā) 02:將驅(qū)動程序添加到內(nèi)核中的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!