[Linux] 最初のドライバー、Hello World! ドライブツアーを開始します

目次

序文: 

 1. 背景

2. ドライバー書き込み処理  

1. APP によって開かれたファイルはカーネル内でどのように表現されますか?

2. ドライバーの作成プロセス

三、ハロードライバー実戦

1.hello_drive.c

2.hello_drive_test.c

3. 最終テスト:

a. まずカーネルをコンパイルします (コンパイルされていない場合)。

b. クロスコンパイル ツールチェーンをセットアップする

c. メイクファイルを作成する

d. オンマシンテスト

e. 最終結果:


序文: 

古典的なリンク:質問を使って考え、練習します。

(1) 最も単純なドライバーは何ですか?

(2) ドライバはどうやって書くのですか?

  • APP によって開かれたファイルはカーネル内でどのように表現されますか?
  • プログラムを書くプロセスを理解する
    • 自作の open/read/write 関数を呼び出すにはどうすればよいですか?
    • 新しいドライバーがあることをカーネルに伝えるにはどうすればよいでしょうか? カーネルはどのようにしてドライバーを見つけるのでしょうか?
    • ドライバーをインストールおよびアンインストールするにはどうすればよいですか?

(3)ハロードライバー実戦

参考になったらたくさん応援していただけると創作意欲が高まります!

 1. 背景

前回の記事を振り返ると、APP が glibc の open/read/write 関数を呼び出すと、ユーザー状態からカーネル状態に移行し、対応する sys_open、sys_read、およびその他の関数を呼び出してカーネル内のファイルにアクセスします。対応するデバイス ノードを開き、対応するドライバーを見つけて起動します。その中で、ユーザーモードからカーネルモードに移行する方法やその他の関連知識については、次の記事を参照してください。

https://blog.csdn.net/weixin_42373086/article/details/129913881?spm=1001.2014.3001.5501

最も単純なドライバーは何ですか?

ここではドライバーを呼び出す最も簡単な方法を示します。ドライバーは、drv_open や drv_read などの独自の関数も提供します。1 つのドライバーが 1 つのデバイスに対応します。

2. ドライバー書き込み処理  

上記に基づいて、単純で基本的なドライバー プログラムを完成するには、open/read/write 関数を自分で実装する必要がありますが、ドライバー プログラムを完全に実装するには、まだいくつかの固有の問題があります。

具体的な問題:

  • APP によって開かれたファイルはカーネル内でどのように表現されますか?
  • プログラムを書くプロセスを理解します。
    • 自作の open/read/write 関数を呼び出すにはどうすればよいですか?
    • 新しいドライバーがあることをカーネルに伝えるにはどうすればよいでしょうか? カーネルはどのようにしてドライバーを見つけるのでしょうか?
    • ドライバーをインストールおよびアンインストールするにはどうすればよいですか?

1. APP によって開かれたファイルはカーネル内でどのように表現されますか?

APP がファイルを開くと、整数 (ファイル ハンドル) を取得できます。APP のファイル ハンドルごとに、カーネル内にそれに対応する「構造体ファイル」が存在します。キャラクタ デバイス ノードを開くと、カーネル内に対応する構造体ファイルも存在します。

open を使用してファイルを開く場合、渡されたフラグやモードなどのパラメーターは、カーネル内の対応する構造体ファイル構造 (f_flags、f_mode) に記録されます。

ファイルの読み取りおよび書き込み時には、ファイルの現在のオフセット アドレスも構造体ファイル構造の f_pos メンバーに格納されます。

キャラクタ デバイス ノードを開くと、カーネル内に対応する構造体ファイルも存在します。注:メンバー内の構造体struct file_operations *f_opはドライバーによって提供されます。

2. ドライバーの作成プロセス

    • ①メインデバイス番号を決定する、またはカーネルに割り当てさせる
    • ②独自の file_operations 構造体を定義します --- 注:ドライバーの open/read/write 関数が含まれています
    • ③ drv_open/drv_read/drv_write などの対応する関数を実装し、file_operations構造体を埋める
    • ④file_operations 構造体をカーネルに伝えます: register_chrdev
    • ⑤ドライバーは誰が登録するのですか?エントリ関数が必要です。ドライバーがインストールされると、このエントリ関数が呼び出されます。
    • ⑥入口関数がある場合は、出口関数が必要です。ドライバーがアンインストールされると、出口関数は unregister_chrdev を呼び出します。
    • ⑦その他の改善: デバイス情報の提供とデバイス ノードの自動作成: class_create、device_create

三、ハロードライバー実戦

ここには最初にドライバープログラムが書かれており、ここの本体はドライバープログラム hello_drive.c とテストプログラム hello_drive_test.c です。

注: ドライバープログラムとアプリケーションプログラム間のデータには、copy_from_user/copy_to_user 関数を使用する必要があります。

1.hello_drive.c

/*
	hello_drive.c
*/

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*第一步:确定主设备号,也可以让内核分配*/
static int major = 0;

//用于保存应用程序下发的数据,定义一个buffer
static char kernel_buffer[1024];
static struct class* hello_class;
#define MIN(a, b) (a < b ? a : b)


/*第三步:实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体*/
static ssize_t hello_drv_read (struct file * file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buffer, MIN(1024, size));
	return MIN(1024, size);
}
static ssize_t hello_drv_write (struct file * file,const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buffer, buf, MIN(1024, size));
	return MIN(1024, size);
}
static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
static int hello_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


/*第二步:定义自己的 file_operations 结构体---注:里面存有驱动的open/read/write函数*/
static struct file_operations hello_drv = {
	.owner 		= THIS_MODULE,
	.open  		= hello_drv_open,
	.read  		= hello_drv_read,
	.write 		= hello_drv_write,
	.release 	= hello_drv_close,
};

/*第四步:把 file_operations 结构体告诉内核:register_chrdev*/
/*第五步:定义一个入口函数, 调用register_chrdev*/
static int __init hello_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "hello", &hello_drv);

	//创建一个类,提供设备信息
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class)){
		unregister_chrdev(major, "hello");
		return -1;
	}

	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");
	return 0;
}


/*第六步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit hello_exit(void)
{
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	major = unregister_chrdev(0, "hello");
}

/*第七步:其他完善:提供设备信息,自动创建设备节点:class_create、device_create*/

//把函数修饰成入口函数和出口函数
module_init(hello_init);
module_exit(hello_exit);

//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("GPL");

2.hello_drive_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	close(fd);
	
	return 0;
}

3. 最終テスト:

a. まずカーネルをコンパイルします (コンパイルされていない場合)。

この記事を参照してくださいhttps://blog.csdn.net/weixin_42373086/article/details/129796348?spm=1001.2014.3001.5501

b. クロスコンパイル ツールチェーンをセットアップする

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

c. メイクファイルを作成する

注: KERN_DIR は開発ボード上のカーネルに対応します。

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
        make -C $(KERN_DIR) M=`pwd` modules
        $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
        rm -f hello_drv_test

obj-m   += hello_drv.o

 次に、make はコンパイルし、.ko ファイルと hello_drive_test を nfs マウントされたフォルダーにコピーします。

cp *.ko hello_drv_test ~/nfs_rootfs/

d. オンマシンテスト

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/hello_drive.ko ./
cp /mnt/hello_drive_test ./
//安装驱动模块
insmod hello_drive.ko

//查询是否有我们的hello程序
cat /proc/devices
lsmod

//查询是否有我们的设备节点
ls /dev/hello -l
//写入
./hello_drive_test -w hello_world!
//读取
./hello_drive_test -r 

e. 最終結果:

 

おすすめ

転載: blog.csdn.net/weixin_42373086/article/details/130545341