コード学習教材は以下から提供されます。
講義 3.5 初めての Linux ドライバー - 完璧な chrdevbase driver_哔哩哔哩_bilibili
個人的な研究/レビュー用にのみ、侵入は削除されます
1. キャラクターデバイスドライバーの概要
キャラクタ デバイスは、Linux ドライバの最も基本的なタイプのデバイス ドライバです。キャラクタ デバイスは、バイト ストリームに従って 1 バイトずつ読み書きするデバイスです。読み書きデータはシーケンシャルな順序です。たとえば、最も一般的な照明、ボタン、i2c、spi、lcd などはすべてキャラクター デバイスであり、これらのデバイスのドライバーはキャラクター デバイス ドライバーと呼ばれます。
Linux カーネル ファイル include/linux/fs.h には、Linux カーネル ドライバーの操作関数を集めた file_operations という構造体があります。私たちは必要なものは何でも使います。一般的に使用されるのは、open、write、read、release、poll、mmap などです。
open: ファイルデバイスをオープンします
読み取り: ファイルデバイスの読み取り
Paul: ポーリング関数。デバイスがノンブロッキング読み取りおよび書き込みを実行できるかどうかを問い合わせるために使用されます。
書き込み: デバイスファイルに書き込みます
release: ファイルデバイスを解放(クローズ)します。アプリケーションのクローズに相当します。
mmap: デバイスのメモリをプロセス空間 (ユーザー空間) にマップします。一般に、LCD ドライバーのビデオ メモリなどのフレーム バッファ デバイスはこの機能を使用します。フレーム バッファがユーザーにマップされた後、スペースを使用すると、アプリケーションはビデオ メモリを直接操作できるため、ユーザー スペースとカーネル スペースの間でコピーを行ったり来たりする必要がありません。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
2. カーネルモジュールのロードとアンロード
Linux ドライバーは、カーネル (zImage) にコンパイルすることも、モジュール .ko にコンパイルすることもできます。テストするときは、.ko ファイルをロードするだけで済みます。
__init 変更された関数は、この関数が初期化フェーズでのみ使用され、この関数によって占有されていたメモリ リソースが使用後に解放されることを示します。
モジュール コードには 2 つの方法があり、1 つは静的にコンパイルしてカーネルにリンクし、システム起動時に初期化する方法、もう 1 つは動的にロードできるモジュールにコンパイルし、insmod を通じてカーネルに再配置する方法です。動的読み込み。これら 2 つの方法は、Makefile の obj-y または obj-m オプションで選択できます。
デバッグ時に obj-y を使用し、コードを送信するときに指定に obj-m を使用できます。
module_init と module_exit に関する 2 つのマクロ参照:
Linux カーネルモジュール解析 (module_init マクロ)_Akimi East のブログ - CSDN blog_module_init
ドライバーモジュールをロードします: insmod drv.ko または modprobe drv.ko
insmod と modprobe の違い:
insmod コマンドでは、モジュールの依存関係の問題を解決できません。たとえば、drv.ko が first.ko モジュールに依存している場合、まず first.ko モジュールを insmod する必要がありますが、modprobe の方が賢明で、指定されたパスに移動して依存関係を見つけます。
ドライバー モジュールをアンインストールします: rmmod drv.ko または modprobe -r drv.ko
ドライバーモジュールを確認してください: lsmod
3. カーネルプリント機能
カーネル印刷関数は printk で、printk には 8 つのレベルがあります。詳細については、include/linux/kern_levels.h を参照してください。
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
0 が最も高い優先順位を持ち、7 が最も低い優先順位を持ちます。メッセージ レベルを設定しない場合、printk はデフォルト レベルのdefault_message_loglevel (7) を選択します。
システム カーネルによって出力されるレベルは、次のコマンドで設定できます。
echo 1 4 1 7 > /proc/sys/kernel/printk
printk のラッパー関数もいくつかあります。
pr_xxx
printk を直接使用してメッセージ レベルを追加するだけでなく、pr_notice、pr_info、pr_warn、pr_err などのインターフェイスも <linux/printk.h> で定義されています。これらの pr_xxx インターフェイスを使用すると、メッセージ レベルを指定する手間を省くことができます。
#define pr_emerg(fmt, ...) printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...) printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...) printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...) printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...) printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...) printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
#define pr_devel(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_debug(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
他の pr_XXX() 関数は無条件に出力できますが、pr_debug() はできないことに注意してください。デフォルトでは、DEBUG が定義されているか、CONFIG_DYNAMIC_DEBUG が設定されていない限りコンパイルされないためです。
dev_xxx
ドライバーについては、dev_err、dev_warn、dev_info などの一部のドライバー モデル診断マクロも <linux/device.h> で提供されます。これらを使用すると、マークされたメッセージ レベルで印刷できるだけでなく、ドライバーのデバッグにとって非常に重要な、対応するデバイスとドライバーの情報も印刷できます。
#define dev_emerg(dev, fmt, ...) _dev_emerg(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_crit(dev, fmt, ...) _dev_crit(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_alert(dev, fmt, ...) _dev_alert(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_err(dev, fmt, ...) _dev_err(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_warn(dev, fmt, ...) _dev_warn(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_notice(dev, fmt, ...) _dev_notice(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_info(dev, fmt, ...) _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
4. キャラクターデバイスの登録と解除
キャラクタ デバイスの場合、ドライバ モジュールが正常にロードされた後にキャラクタ デバイスを登録する必要があり、同様に、ドライバ モジュールをアンインストールするときにキャラクタ デバイスの登録を解除する必要があります。
// 注册字符设备函数
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
// 注销字符设备函数
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
register_chrdev 関数はキャラクタデバイスを登録するために使用され、この関数には合計 3 つのパラメータがあり、その意味は次のとおりです。
Major : メジャー デバイス番号。Linux 下の各デバイスにはデバイス番号があり、デバイス番号はメジャー デバイス番号とマイナー デバイス番号の 2 つの部分に分かれています。
Linux では、メジャー デバイス番号は特定のドライバーを示し、マイナー デバイス番号はこのドライバーを使用する各デバイスを示します。Linux では、デバイス番号を示すために dev_t という名前のデータ型が提供されます。Dev_t は include/linux/types.h で定義されています。次のように:
typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
まとめると、dev_t 型は実際には unsigned int 型であり、unsigned int 型のデータです。
32 ビットの最初の 10 桁がメイン デバイス番号、下位 20 ビットがマイナー デバイス番号であるため、Linux システムのメジャー デバイス番号の範囲は 0 ~ 4095 であるため、この範囲を超えてはなりませんメジャーデバイス番号を選択する場合 include/linux/ では、デバイス番号に関するいくつかのマクロが kdev_t.h に提供されています。
#define MINORBITS 20 // 次设备号位数
#define MINORMASK ((1U << MINORBITS) - 1) // 次设备号掩码
// 从设备号中获取主设备号,dev_t右移20位
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
// 从设备号获取次设备号,取dev_t的低20位
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
// 将给定的主设备号和次设备号组合成dev_t类型的设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
次のようにcat /proc/devicesコマンドを使用すると、現在のシステム内のすべてのデバイスを表示できます。
メジャー デバイス番号 50 が使用されていないことがわかり、次のようにメジャー デバイス番号 50 を使用できます。
register_chrdev(50, "chrdevbase",const struct file_operations *fops);
name : 文字列の文字列を指すデバイス名
fops : デバイスの操作関数の設定変数を指す構造体 file_operations 型ポインタ
unregister_chrdev 関数も同様です。
いくつかの重要な構造:
構造体 inode: 静的ファイル
struct file: 動的ファイル (開かれたファイル)、ファイル構造内に private_data というメンバー変数があります。
struct file_operations: デバイスの操作方法を記述します。
struct cdev: struct cdev 構造体はキャラクタ デバイスを記述するために使用され、各キャラクタ デバイスは struct cdev 構造体に対応します。
ドライバー内の 3 つの重要な構造体 (struct inode、struct file、struct file_operations) の紹介
5. 人間の利用
ドライバーを作成したら、作成したドライバーが正しいかどうかをテストするアプリケーションを作成する必要があります。
マニュアルの呼び出しは次のとおりです (写真はインターネットからのものです)。
たとえば、open システム コールでは、man 2 openを通じて関連するヘッダー ファイルと注意事項を確認できます。
6. chrdevbaseのドライバーデモ
/dev と入力して、モジュール名に基づいて名前が付けられたデバイスを表示します。最初にデバイス ノードを手動で作成できます。
mknod /dev/chrdevbase c 200 0 (c はキャラクターデバイスを表し、メジャーデバイス番号は 200、マイナーデバイス番号は 0)
chrdevbase.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#define CHRDEVBASE_MAJOR 200
#define CHRDEVBASE_NAME "chrdevbase"
static int chrdevbase_open(struct inode *inode, struct file *file)
{
printk("chrdevbase_open start\n");
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *file)
{
printk("chrdevbase_release start \n");
return 0;
}
// count:读取的数据量大小
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
printk("chrdevbase_read start\n");
return 0;
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
printk("chrdevbase_write start\n");
return 0;
}
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.release = chrdevbase_release,
.read = chrdevbase_read,
.write = chrdevbase_write,
};
static int __init chrdevbase__init(void)
{
int ret = 0;
printk("chrdevbase__init \n");
// 返回值小于0就申请失败了
ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
if(ret < 0) {
printk("chrdevbase__init failed!\n");
}
printk("chrdevbase__init start\n");
return 0;
}
static void __exit chrdevbase__exit(void)
{
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
printk("chrdevbase__exit end\n");
}
module_init(chrdevbase__init);
module_exit(chrdevbase__exit);
テストプログラム chrdevbaseAPP.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, int *argv[])
{
char *filename;
int fd;
int ret;
filename = argv[1];
char readbuf[100], writebuf[100];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("open %s failed\n", filename);
return -1;
}
ret = read(fd, readbuf, 50);
if (ret < 0) {
printf("read %s failed\n", filename);
return -1;
} else {
}
ret = write(fd, writebuf, 50);
if (ret < 0) {
printf("write %s failed\n", filename);
return -1;
} else {
}
ret = close(fd);
if (ret < 0) {
printf("close %s failed\n", filename);
return -1;
} else {
}
return 0;
}
テストを実行します。
./chrdevbaseAPP /dev/chrdevbase
7. chrdevbaseドライバーの改善
ドライバーはアプリケーションにデータを転送するときに copy_to_user を使用する必要があり、アプリケーションはドライバーにデータを転送するときに copy_from_user を使用します。定義は次のとおりです。
static inline long copy_to_user(void __user volatile *to, const void *from,
unsigned long n)
static inline long copy_from_user(void *to, const void __user volatile *from,
unsigned long n)
返回参数:成功返回0,失败返回负数
chrdevbase.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <linux/fs.h>
#include <linux/io.h>
#define CHRDEVBASE_MAJOR 200
#define CHRDEVBASE_NAME "chrdevbase"
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};
static int chrdevbase_open(struct inode *inode, struct file *file)
{
printk("chrdevbase_open start\n");
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *file)
{
printk("chrdevbase_release start \n");
return 0;
}
// count:读取的数据量大小
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
memcpy(readbuf, kerneldata, sizeof(kerneldata));
int ret = copy_to_user(buf, readbuf, count);
if (ret == 0) {
printk("copy to user success \n");
} else {
}
return 0;
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
ret = copy_from_user(writebuf, buf, count);
if (ret == 0) {
printk("copy from user success, recedata = %s \n", writebuf);
} else {
}
return 0;
}
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.release = chrdevbase_release,
.read = chrdevbase_read,
.write = chrdevbase_write,
};
static int __init chrdevbase__init(void)
{
int ret = 0;
printk("chrdevbase__init \n");
// 返回值小于0就申请失败了
ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
if(ret < 0) {
printk("chrdevbase__init failed!\n");
}
printk("chrdevbase__init start\n");
return 0;
}
static void __exit chrdevbase__exit(void)
{
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
printk("chrdevbase__exit end\n");
}
module_init(chrdevbase__init);
module_exit(chrdevbase__exit);
chrdevbaseAPP.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
* ./chrdevbaseAPP <filename> <1/2>
* 1 读数据
* 2 写数据
* */
int main(int argc, int *argv[])
{
char *filename;
int fd;
int ret;
filename = argv[1];
char readbuf[100], writebuf[100];
if (argc < 3) {
printf("usage error \n");
}
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("open %s failed\n", filename);
return -1;
}
if(atoi(argv[2]) == 1) {
ret = read(fd, readbuf, 50);
if (ret < 0) {
printf("read %s failed\n", filename);
return -1;
} else {
printf("APP read data: %s \n", readbuf);
}
}
if(atoi(argv[2]) == 2) {
memcpy(writebuf, usrdata, sizeof(userdata));
ret = write(fd, writebuf, 50);
if (ret < 0) {
printf("write %s failed\n", filename);
return -1;
} else {
printf("APP write data: %s \n", readbuf);
}
}
ret = close(fd);
if (ret < 0) {
printf("close %s failed\n", filename);
return -1;
}
return 0;
}
テストを実行します。
./chrdevbaseAPP /dev/chrdevbase 1