經(jīng)過(guò)以下四個(gè)步驟,終于可以開(kāi)始驅(qū)動(dòng)開(kāi)發(fā)了
01.安裝交叉編譯環(huán)境【附下載地址】
02.IMX6ULL燒寫(xiě)Linux系統(tǒng)
03.設(shè)置IMX6ULL開(kāi)發(fā)板與虛擬機(jī)在同一網(wǎng)段
04.IMX6ULL開(kāi)發(fā)板與虛擬機(jī)互傳文件
目錄
一、獲取內(nèi)核、編譯內(nèi)核
二、創(chuàng)建vscode工作區(qū),添加內(nèi)核目錄和個(gè)人目錄
三、了解驅(qū)動(dòng)程序編寫(xiě)流程
四、第一個(gè)驅(qū)動(dòng)程序 - hello驅(qū)動(dòng)
五、IMX6ULL驗(yàn)證hello驅(qū)動(dòng)
一、獲取內(nèi)核、編譯內(nèi)核
1、獲取內(nèi)核文件
獲取Linux內(nèi)核文件,可以從Linux Kernel官網(wǎng)下載,我這里為了跟開(kāi)發(fā)板中的系統(tǒng)一致,避免出現(xiàn)其他問(wèn)題,所以使用的韋東山老師提供的Linux-4.9.88內(nèi)核文件,需要自取
鏈接:https://pan.baidu.com/s/111M2FsgJXAPsQ3ppeVwbFQ
提取碼:p7wp
2、編譯內(nèi)核文件
為什么要編譯內(nèi)核文件,因?yàn)轵?qū)動(dòng)代碼的編譯要基于編譯好的內(nèi)核文件的
在編譯之前,要在~/.bashrc文件下添加兩行內(nèi)容,來(lái)指定編譯的平臺(tái)和工具鏈
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
編譯內(nèi)核步驟:(如果中途報(bào)錯(cuò)了,自行百度一下就行,網(wǎng)上很多解決辦法的,這里就不一一列舉了)
- 先刪除之前編譯所生成的文件和配置文件,備份文件等
make mrproper
成功現(xiàn)象:無(wú)內(nèi)容輸出
- 設(shè)置內(nèi)核的相關(guān)配置
make 100ask_imx6ull_defconfig
成功現(xiàn)象:
HOSTCC scripts/basic/fixdepHOSTCC scripts/kconfig/conf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCCscripts /kconfig/zconf.tab.o
HOSTLDscripts/kconfig/conf
#
#configuration written to .config
#
- 生成鏡像文件(-j4會(huì)快一些,代表使用4個(gè)核心一起編譯,具體用幾個(gè)核心根據(jù)自己虛擬機(jī)具體情況而定)
make zImage -j4
成功現(xiàn)象:在 內(nèi)核文件/arch/arm/boot/目錄下 生成 zImage 文件,且沒(méi)有報(bào)錯(cuò)
- 生成設(shè)備樹(shù)(這一步也做一下,很快的,雖然我們用不到,就跟著步驟來(lái)嘛)
make dtbs
成功現(xiàn)象:輸出幾行內(nèi)容,無(wú)報(bào)錯(cuò)
- 在家目錄下新建 nfs_rootfs 目錄,將鏡像文件、生成的設(shè)備樹(shù)拷貝其目錄下 (可不做)
cp arch/arm/boot/zImage ~/nfs_rootfs
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs
- 編譯內(nèi)核模塊
make modules
成功現(xiàn)象:輸出很多.o文件,最后輸出一些.ko文件,無(wú)報(bào)錯(cuò)
二、配置vscode,添加內(nèi)核目錄和個(gè)人目錄
- 使用vscode打開(kāi)linux-4.9.88內(nèi)核目錄,點(diǎn)擊左上角 “文件” ,“將工作區(qū)另存為”,選擇家目錄下,隨便起個(gè)名字
- 使用vscode打開(kāi)自己要編寫(xiě)驅(qū)動(dòng)的文件目錄,右鍵自己的文件夾,選擇“ 將文件夾添加到工作區(qū)”,選擇自己創(chuàng)建的那個(gè)工作區(qū)
- 至此就可以在vscode同一個(gè)工作區(qū)下看到自己的驅(qū)動(dòng)文件和linux內(nèi)核文件了,方便后續(xù)獨(dú)照內(nèi)核文件內(nèi)容
- 在.vscode 下的c_cpp_properties.json 文件中設(shè)置工作路徑
"/home/me/Linux-4.9.88/tools/virtio",
"/home/me/Linux-4.9.88/include/**",
"/home/me/Linux-4.9.88/include/linux/**",
"/home/me/Linux-4.9.88/arch/arm/include/**",
"/home/me/Linux-4.9.88/arch/arm/include/generated/**"
三、了解驅(qū)動(dòng)程序編寫(xiě)流程
1. 先看內(nèi)核目錄下原有的驅(qū)動(dòng)是怎么寫(xiě)的
打開(kāi)Linux-4.9.88/drivers/char目錄(看名字就猜到該目錄下存放應(yīng)該是字符驅(qū)動(dòng)代碼)
發(fā)現(xiàn)有個(gè) ds1602.c ,打開(kāi)看看它是怎么寫(xiě)的(因?yàn)槲覍W(xué)過(guò)了,選擇這個(gè)文件,展示主要代碼,有助于入門(mén)理解)
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <mach/hardware.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>
static DEFINE_MUTEX(ds1620_mutex);
static const char *fan_state[] = { "off", "on", "on (hardwired)" };
.....
..... 省略
.....
static int __init ds1620_init(void)
{
int ret;
struct therm th, th_start;
if (!machine_is_netwinder())
return -ENODEV;
ds1620_out(THERM_RESET, 0, 0);
.....
..... 省略
.....
static int ds1620_open(struct inode *inode, struct file *file)
{
return nonseekable_open(inode, file);
}
.....
..... 省略
.....
static ssize_t ds1620_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
signed int cur_temp;
signed char cur_temp_degF;
cur_temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;
/* convert to Fahrenheit, as per wdt.c */
cur_temp_degF = (cur_temp * 9) / 5 + 32;
if (copy_to_user(buf, &cur_temp_degF, 1))
return -EFAULT;
return 1;
}
.....
..... 省略
.....
static const struct file_operations ds1620_fops = {
.owner = THIS_MODULE,
.open = ds1620_open,
.read = ds1620_read,
.unlocked_ioctl = ds1620_unlocked_ioctl,
.llseek = no_llseek,
};
.....
..... 省略
.....
static void __exit ds1620_exit(void)
{
#ifdef THERM_USE_PROC
remove_proc_entry("therm", NULL);
#endif
misc_deregister(&ds1620_miscdev);
}
module_init(ds1620_init);
module_exit(ds1620_exit);
MODULE_LICENSE("GPL");
2. 驅(qū)動(dòng)程序主要構(gòu)成(主要是的前面四個(gè))
· file_operations 結(jié)構(gòu)體 : 為系統(tǒng)調(diào)用提供驅(qū)動(dòng)程序入口的結(jié)構(gòu)體
· module_init : 定義驅(qū)動(dòng)模塊的入口函數(shù)
· module_exit : 定義驅(qū)動(dòng)模塊的退出函數(shù)
· MODULE_LICENSE(“GPL”) : 聲明模塊許可證,指明這是GNU General Public License的任意版本,
?????????????????????????????????????????????????????否則在加載此模塊時(shí),會(huì)收到內(nèi)核被污染 “kernel tainted” 的警告
· ds1620_init :ds1602初始化函數(shù)
· ds1620_open : 打開(kāi)ds1602設(shè)備函數(shù)
· ds1620_read : 讀ds1602設(shè)備函數(shù)
· ds1620_exit : ds1602退出驅(qū)動(dòng)函數(shù)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
3. 驅(qū)動(dòng)程序?qū)崿F(xiàn)過(guò)程及調(diào)用原理
· module_init 指定驅(qū)動(dòng)入口函數(shù)
· 設(shè)備對(duì)象通過(guò) struct file_operations 結(jié)構(gòu)體定義
· 入口函數(shù)中調(diào)用 register_chrdev 函數(shù),傳入注冊(cè)定義好的驅(qū)動(dòng)設(shè)備變量,生成設(shè)備號(hào)
· 在開(kāi)發(fā)板上通過(guò) insmod 命令將編譯好的驅(qū)動(dòng)模塊(.ko文件,要從虛擬機(jī)傳到板子上哦)載入內(nèi)核
· 通過(guò)應(yīng)用程序可以對(duì)設(shè)備進(jìn)行讀寫(xiě)等操作,讀寫(xiě)等操作通過(guò)系統(tǒng)調(diào)用 register_chrdev 結(jié)構(gòu)體中指定的驅(qū)動(dòng)模塊讀寫(xiě)等函數(shù)來(lái)實(shí)現(xiàn)
· close設(shè)備時(shí),系統(tǒng)調(diào)用 module_exit 指定的驅(qū)動(dòng)模塊退出函數(shù)
以上便是驅(qū)動(dòng)程序?qū)崿F(xiàn)過(guò)程及應(yīng)用程序調(diào)用驅(qū)動(dòng)程序的原理(按照個(gè)人理解寫(xiě)的,大概是這么個(gè)流程,有不恰當(dāng)?shù)牡胤綒g迎指出)
四、第一個(gè)驅(qū)動(dòng)程序 - hello驅(qū)動(dòng)
1. 在個(gè)人目錄下新建 hello_drv.c 文件
2. 照葫蘆畫(huà)瓢,把上面 ds1602 用到的頭文件**都復(fù)制過(guò)來(lái)
3. 繼續(xù)照葫蘆畫(huà)瓢編寫(xiě) file_operations 結(jié)構(gòu)體**
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.read = hello_read,
.write = hello_write,
.open = hello_open,
.release = hello_release,
};
假裝我們的驅(qū)動(dòng)也可以讀、寫(xiě),當(dāng)然也必須有open和release(就是close)
當(dāng)然 .owner = THIS_MODULE, 也必須有,原因見(jiàn)博客https://blog.csdn.net/a954423389/article/details/6101369
4. 研究file_operations結(jié)構(gòu)體
每個(gè)函數(shù)都有對(duì)應(yīng)的模板,可不是亂寫(xiě)的,因?yàn)檫@些函數(shù)組中都會(huì)被系統(tǒng)調(diào)用,參數(shù)都是固定的,可以按住ctrl鍵,用鼠標(biāo)點(diǎn)擊file_operations,跳轉(zhuǎn)到該結(jié)構(gòu)體定義處,可以看到每個(gè)函數(shù)指針的形式
5. 照葫蘆畫(huà)瓢實(shí)現(xiàn)hello_read、hello_write、hello_open、hello_release函數(shù)
/*養(yǎng)成好習(xí)慣,驅(qū)動(dòng)程序都加static修飾*/
static int hello_open (struct inode *node, struct file *filp)
{
printk("hello_open\n");
printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
printk("hello_read\n");
return size; //返回讀取字節(jié)數(shù)
}
static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
printk("hello_write\n");
return size; //返回寫(xiě)入字節(jié)數(shù)
}
static int hello_release (struct inode *node, struct file *filp)
{
printk("hello_release\n");
return 0;
}
6. 編寫(xiě)hello驅(qū)動(dòng)的入口函數(shù)、出口函數(shù)
入口函數(shù)需要用到 register_chrdev 函數(shù)
出口函數(shù)(退出函數(shù))需要用到 unregister_chrdev 函數(shù),但是 ds1602.c 中沒(méi)有這兩個(gè)函數(shù)
沒(méi)關(guān)系,我們?cè)趘scode中搜索 register_chrdev, 隨便點(diǎn)一個(gè)看一下,研究一下用法(實(shí)在看不懂百度一下哈哈)
也可以用linux命令查找(在內(nèi)核的drivers/char目錄下查找)
grep "register_chrdev" * -nwr
我們用vscode找一下
發(fā)現(xiàn) register_chrdev 函數(shù)需要三個(gè)參數(shù)
第一個(gè)參數(shù)是主設(shè)備號(hào),0代表動(dòng)態(tài)分配
第二個(gè)參數(shù)是設(shè)備的名字(自定義)
第三個(gè)參數(shù)是struct file_operations結(jié)構(gòu)體類(lèi)型的指針,代表申請(qǐng)?jiān)O(shè)備的操作函數(shù)
同理,出口函數(shù) unregister_chrdev
第一個(gè)參數(shù)是設(shè)備號(hào)
第二個(gè)參數(shù)是設(shè)備名稱
照葫蘆畫(huà)瓢開(kāi)始寫(xiě)
/*入口函數(shù)*/
static int major;
static int hello_init(void)
{
/*返回設(shè)備號(hào),定義設(shè)備名稱為hello_drv*/
major = register_chrdev(0,"hello_drv",&hello_drv);
return 0;
}
/*退出函數(shù)*/
static int hello_exit(void)
{
unregister_chrdev(major,"hello_drv");
return 0;
}
7. module_init 、module_exit和聲明許可證
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
整個(gè) hello_drv.c 代碼
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/therm.h>
static int major;
static int hello_open (struct inode *node, struct file *filp)
{
printk("hello_open\n");
printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t hello_read (struct file *filp, char *buf, size_t size, loff_t *offset)
{
printk("hello_read\n");
return size; //返回讀取字節(jié)數(shù)
}
static ssize_t hello_write (struct file *filp, const char *buf, size_t size, loff_t *offset)
{
printk("hello_write\n");
return size; //返回寫(xiě)入字節(jié)數(shù)
}
static int hello_release (struct inode *node, struct file *filp)
{
printk("hello_release\n");
return 0;
}
/*1.定義 file_operations 結(jié)構(gòu)體*/
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.read = hello_read,
.write = hello_write,
.open = hello_open,
.release = hello_release,
};
/*2.register_chrdev*/
/*3.入口函數(shù)*/
static int hello_init(void)
{
//設(shè)備號(hào)
major = register_chrdev(0,"hello_drv",&hello_drv);
return 0;
}
/*4.退出函數(shù)*/
static int hello_exit(void)
{
//卸載驅(qū)動(dòng)
unregister_chrdev(major,"hello_drv");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
8. 編寫(xiě)Makefile
不多說(shuō),直接上代碼
KERN_DIR = /home/me/Linux-4.9.88
PWD ?= $(shell KERN_DIR)
all:
make -C $(KERN_DIR) M=$(PWD) modules
#$(CROSS_COMPILE)gcc -o hello_test hello_test.c
clean:
make -C $(KERN_DIR) M=$(PWD) modules clean
rm -rf modules.order
rm -f hello_drv
obj-m += hello_drv.o
KERN_DIR = /home/me/Linux-4.9.88 : 編譯程序的依賴目錄
9. make一下,生成.ko文件
me@ubuntu:~/Linux_ARM/IMX6ULL/hello_driver$ ls
hello_drv.c hello_drv.ko hello_drv.mod.c hello_drv.mod.o hello_drv.o
hello_test hello_test.c Makefile modules.order Module.symvers
主要就是.ko文件,就是加載到內(nèi)核里的驅(qū)動(dòng)模塊文件
五、IMX6ULL驗(yàn)證hello驅(qū)動(dòng)
1. 將虛擬機(jī)編譯生成的.ko文件拷貝到IMX6ULL開(kāi)發(fā)板上
這步操作需要虛擬機(jī)和開(kāi)發(fā)板在同一網(wǎng)段下,可以借鑒以下兩篇博客
03.設(shè)置IMX6ULL開(kāi)發(fā)板與虛擬機(jī)在同一網(wǎng)段
04.IMX6ULL開(kāi)發(fā)板與虛擬機(jī)互傳文件
我采用的是NFS掛載的方式
mount -t nfs -o nolock,vers=3 192.168.1.200:/home/me/Linux_ARM/IMX6ULL/hello_driver /mnt
如果出現(xiàn)報(bào)錯(cuò)
failed: Device or resource busy
執(zhí)行
unmount /mnt
2.加載內(nèi)核
執(zhí)行命令加載內(nèi)核
insmod hello_drv.ko
[root@100ask:/mnt]# insmod hello_drv.ko
[ 80.794911] hello_drv: loading out-of-tree module taints kernel.
[root@100ask:/mnt]#
顯示已載入系統(tǒng)的模塊
lsmod
查看 hello_drv.ko 驅(qū)動(dòng)模塊的設(shè)備號(hào)
cat /proc/devices
226 drm
240 hello_drv
241 adxl345
242 spidevx
243 irda
244 dht11
可以看到hello驅(qū)動(dòng)的設(shè)備號(hào)是240
3.生成設(shè)備節(jié)點(diǎn)
mknod /dev/hello c 240 0
/dev/hello : 生成設(shè)備節(jié)點(diǎn)的名稱
c :說(shuō)明是字符設(shè)備
240 : 主設(shè)備號(hào)
0 : 子設(shè)備號(hào)(不指定子設(shè)備號(hào),為0)
這時(shí)候查看hello設(shè)備也可以用下面的命令
[root@100ask:/mnt]# ls /dev/hello -l
crw-r--r-- 1 root root 240, 0 Jan 1 11:34 /dev/hello
4.編寫(xiě)應(yīng)用程序驗(yàn)證驅(qū)動(dòng)
PS:在第四節(jié)第8小節(jié)中,將 $(CROSS_COMPILE)gcc -o hello_test hello_test.c 這一行注釋去掉
編寫(xiě) hello_test.c 應(yīng)用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int len;
char read_buf[10];
if(argc < 2){
printf("please input at least 2 args\n");
printf("%s <dev> [string]\n", argv[0]);
return -1;
}
/*open*/
int fd;
fd = open(argv[1], O_RDWR);
if(fd < 0){
printf("open failed\n");
return -2;
}
/*read*/
if(argc == 2){
read(fd, read_buf, 10); //調(diào)用read函數(shù),只為了觸發(fā)系統(tǒng)調(diào)用hello驅(qū)動(dòng)的read函數(shù)
printf("read operation \n");
}
/*write*/
if(argc == 3){
len = write(fd, argv[2], strlen(argv[2])); //調(diào)用write函數(shù),只為了觸發(fā)系統(tǒng)調(diào)用hello驅(qū)動(dòng)的write函數(shù)
printf("write length = %d \n", len);
}
close(fd);
return 0;
}
該程序的使用方式:
./hello_test /dev/hello 123abc 兩個(gè)參數(shù):模擬寫(xiě)操作
./hello_test /dev/hello 一個(gè)參數(shù):模擬讀操作
/dev/hello 是我們要打開(kāi)的設(shè)備名稱,在應(yīng)用程序中,使用 open 函數(shù)打開(kāi),使用 close 函數(shù)關(guān)閉
如果打開(kāi)成功,則系統(tǒng)會(huì)調(diào)用 hello 驅(qū)動(dòng)的 open 函數(shù),我們就會(huì)看到相應(yīng)的打印信息(打印出當(dāng)前文件名,函數(shù)名,行數(shù))
static int hello_open (struct inode *node, struct file *filp)
{
printk("hello_open\n");
printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
return 0;
}
注意驅(qū)動(dòng)程序中使用的都是printk函數(shù),該函數(shù)是內(nèi)核調(diào)用的,想要在開(kāi)發(fā)板的串口打印信息中看到輸出,需要執(zhí)行以下命令
echo "7 4 1 7" > /proc/sys/kernel/printk
測(cè)試驅(qū)動(dòng)寫(xiě)操作,成功?。?!
[root@100ask:/mnt]# ./hello_test /dev/hello
[ 499.512588] hello_open
[ 499.516872] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[ 499.525082] hello_read
read operation [ 499.528427] hello_release
[root@100ask:/mnt]#
測(cè)試驅(qū)動(dòng)讀操作,成功!?。?mark hidden color="red">文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-735606.html
[root@100ask:/mnt]# ./hello_test /dev/hello abc123
[ 500.725340] hello_open
[ 500.727762] /home/me/Linux_ARM/IMX6ULL/hello_driver/hello_drv.c hello_open 28
[ 500.736217] hello_write
write length = 6 [ 500.739735] hello_release
[root@100ask:/mnt]#
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-735606.html
到了這里,關(guān)于【IMX6ULL驅(qū)動(dòng)開(kāi)發(fā)學(xué)習(xí)】05.IMX6ULL驅(qū)動(dòng)開(kāi)發(fā)_編寫(xiě)第一個(gè)hello驅(qū)動(dòng)【熬夜肝】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!