記事の枠組みは前章に基づいています
https://blog.csdn.net/qq_52749711/article/details/132409329
はほぼ同じです。ここでの名前は変更されていますが、由来はそのままです。
記事ディレクトリ
module_init エントリ関数
ユーザー モードの main 関数と同様に、括弧内の関数を呼び出して実行します。内部の関数はエントリ関数です。
次に例を示します。
static int pin5_drv_init(void);//函数声明
module_init(pin5_drv_init)
関数には登録ドライバーなどのコードが含まれています
module_exit 終了関数
実行後に実行する必要がある関数。原理は上記と同じです。
module_exit(pin5_drv_exit);
この関数には、ドライバーなどをアンインストールするためのコードが含まれています。
register_chrdev
register_chrdev
function は、キャラクター デバイス ドライバーを登録するために使用される Linux カーネル内の関数の 1 つです。キャラクター デバイスは、端末、シリアル ポートなど、キャラクター ストリームと対話するデバイスです。Linux カーネルでは、キャラクター デバイス ドライバーは、ファイル操作関数を通じてキャラクター デバイスの読み取りおよび書き込みなどの操作を実装します。
通常、関数は次のように宣言されます。
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
パラメータの説明:
major
: メジャー デバイス番号を指定します。キャラクター デバイスの場合、メジャー デバイス番号はデバイス ドライバーを一意に識別する番号です。MAJOR(dev_t dev)
メジャーデバイス番号はマクロを通じて取得できます。
name
: /proc/devices にデバイス名を表示するためのデバイス名を指定します。
fops
:file_operations
読み出し、書き込み、オープン、クローズなどのキャラクタデバイスドライバの操作関数を含む構造体へのポインタ。
戻り値:
登録が成功すると、割り当てられたメジャー デバイス番号が返されます。
登録が失敗すると、負の値 (通常はエラー コード) が返されます。
この関数を使用してregister_chrdev
キャラクターデバイスドライバーを登録した後、モジュールの初期化関数でこの関数を呼び出す必要があります。例えば:
#include <linux/module.h>
#include <linux/fs.h>
static int my_chardev_open(struct inode *inode, struct file *file)
{
// Open operation implementation
return 0;
}
static int my_chardev_release(struct inode *inode, struct file *file)
{
// Release operation implementation
return 0;
}
static struct file_operations my_fops = {
.open = my_chardev_open,
.release = my_chardev_release,
// Other operation implementations
};
static int __init my_chardev_init(void)
{
int major = register_chrdev(0, "my_chardev", &my_fops);
if (major < 0) {
printk(KERN_ALERT "Failed to register char device\n");
return major;
}
printk(KERN_INFO "Registered char device with major number %d\n", major);
return 0;
}
static void __exit my_chardev_exit(void)
{
unregister_chrdev(major, "my_chardev");
printk(KERN_INFO "Unregistered char device\n");
}
module_init(my_chardev_init);
module_exit(my_chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Character Device Driver");
my_chardev_open (オープン関数)
my_chardev_open
これはキャラクター デバイス ドライバー内の関数であり、デバイスを開く操作を処理するために使用されます。struct file_operations
キャラクタ デバイスを登録するときは、構造体に独自に実装した open 関数へのポインタを設定する必要があります。デバイスがオープンされると、カーネルはこの関数を呼び出します。
これは my_chardev_open 関数の定義です。
static int my_chardev_open(struct inode *inode, struct file *file)
{
// Open operation implementation
return 0;
}
この機能では、デバイスの状態の初期化、リソースの割り当て、デバイスがオープンされた回数の記録など、デバイスのオープン操作に関連するタスクを実行できます。この関数は 2 つのパラメータを受け取ります。
struct inode *inode
: これはファイルのインデックス ノード (inode) へのポインタであり、ファイルに関するメタデータ情報が含まれています。デバイスがオープンされると、カーネルは関連する i ノードを open 関数に渡します。
-
構造体 inode *inode:
-
inode
(インデックス ノード) には、ファイルのアクセス許可、サイズ、ユーザーなどのファイルのメタデータ情報が含まれます。ファイル システム内のファイルを一意に識別します。フィールド
を使用してファイルの権限情報を取得し、フィールドとフィールドを使用してファイルのユーザー ID とグループ ID を取得できます。i_mode
i_uid
i_gid
-
i_private
フィールドを使用して、デバイスに関連するプライベート データを保存できます。このフィールドは、ドライバーの初期化中に設定できます。
struct file *file
: これはファイルを表すデータ構造へのポインタです。これには、アクセス モード、ファイルの場所など、ファイル操作に関連する情報が含まれます。
-
構造体ファイル *ファイル:
-
ファイル構造には、ファイルの場所、アクセス モードなど、ファイルを開くことに関連する情報が含まれています。
-
f_pos フィールドは、ファイルの現在位置オフセットを示します。
-
f_flags フィールドには、読み取り、書き込み、追加など、ファイルを開くときに使用されるフラグが含まれます。
-
f_mode フィールドには、ファイルを開くときのアクセス モードが含まれており、ビット操作によって決定できます。
-
private_data フィールドは、ファイル操作に関連するプライベート データを保存するために使用でき、ファイルを開くときに設定できます。
関数の戻り値は整数で、通常は操作が成功したかどうかを示すために使用されます。オープン操作が成功した場合は、通常、エラーが発生しなかったことを示す 0 が返されます。エラーが発生した場合、別のエラー コードに対応して負の値が返されることがあります。
my_chardev_open
以下は、関数でオープン操作を実装する方法を示す例です。
static int my_chardev_open(struct inode *inode, struct file *file)
{
// Perform device-specific tasks during open, if any
printk(KERN_INFO "Device opened\n");
// Increment the device's open count (if you want to track it)
try_module_get(THIS_MODULE);
return 0; // Return 0 to indicate success
}
上記の例では、printk
デバイスが開かれたことを示すログを出力する関数を使用しています。デバイスが開かれた回数を追跡したい場合は、 をtry_module_get(THIS_MODULE)
使用してカーネル モジュールの参照カウントを増分できます。このようにして、デバイスのシャットダウン時にmodule_put(THIS_MODULE)
参照カウントをデクリメントできます。
つまり、my_chardev_open
キャラクターデバイスをオープンする際に何らかの操作を行うことができる機能であり、デバイスの特性やニーズに応じて、適切なオープン操作コードを記述することができます。
static int my_chardev_open(struct inode *inode, struct file *file)
{
// 访问 inode 信息
printk(KERN_INFO "文件权限: %o\n", inode->i_mode & 0777);
printk(KERN_INFO "文件所有者用户ID: %d\n", inode->i_uid.val);
printk(KERN_INFO "文件所有者组ID: %d\n", inode->i_gid.val);
// 访问文件信息
printk(KERN_INFO "文件位置: %lld\n", file->f_pos);
printk(KERN_INFO "文件标志: %x\n", file->f_flags);
// 在文件结构中设置 private_data
file->private_data = /* 在此处添加你的私有数据 */;
return 0;
}
書き込み関数
関数プロトタイプは、pin5_write
ユーザー空間からデバイスにデータを書き込むために使用される、キャラクター デバイス ドライバーの書き込み操作関数に似ています。この関数のパラメータの意味を説明しましょう。
file
: これは開いているファイルを表す構造体ファイル ポインターであり、開いているファイルに関連する情報が含まれています。このパラメータは書き込むファイルを指定します。
buf
: これは、デバイスに書き込まれるデータを含むユーザー空間バッファーへのポインターです。__user は、これがユーザー空間データであるため、カーネル空間で注意して扱う必要があることを示すフラグです。
count
: これは書き込むバイト数で、バッファ内のデータの長さを指定します。
ppos
: ファイルの現在位置オフセットを示す loff_t 型へのポインタです。書き込み操作中に、カーネルはこの場所を更新する必要がある場合があります。
関数の戻り値は ssize_t 型で、書き込まれたバイト数を示します。エラーが発生した場合は、別のエラー コードに対応する負の値が返されます。
以下は、pin5_write 関数の動作例を簡略化して実装したものです。
static ssize_t pin5_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin5_write\n");
return 0;
}
実際のドライバ開発では、デバイスの特性や要件に応じて pin5_write 関数を実装する必要があります。たとえば、必要に応じて、デバイスへのデータの書き込みやファイル オフセットの更新などの操作を追加できます。同時に、カーネル空間とユーザー空間の間でデータのコピーと検証が適切に行われるようにして、セキュリティと安定性を確保します。
#include <linux/fs.h>
#include <linux/uaccess.h>
// 假设你的设备在打开时已经被初始化为pin5设备
static ssize_t pin5_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
ssize_t written = 0;
// 验证用户空间内存并将数据从用户空间复制到内核空间
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
// 在内核中分配一个临时缓冲区
char *kernel_buf = kmalloc(count, GFP_KERNEL);
if (!kernel_buf)
return -ENOMEM;
// 从用户空间复制数据到内核缓冲区
if (copy_from_user(kernel_buf, buf, count)) {
kfree(kernel_buf);
return -EFAULT;
}
// 在这里执行将数据写入设备的操作,示例中省略
// 记录写入的字节数
written = count;
// 释放临时分配的内存
kfree(kernel_buf);
// 更新文件位置偏移
*ppos += written;
return written;
}
上記のコードでは、最初にユーザー空間メモリがアクセス可能かどうかを確認し、access_ok
関数を使用してこれを実現します。次に、カーネル空間に一時バッファを割り当て、copy_from_user
関数を使用してユーザー空間からカーネル空間にデータをコピーしました。
次の部分は、カーネル バッファからデバイスにデータを書き込む実際の操作です。これには、デバイスの特性に基づいて実装できるデバイス レジスタの操作、データ送信などが含まれる場合があります。
最後に、書き込まれたバイト数を記録し、一時的に割り当てられたメモリを解放します。また、書き込み操作後に正しいファイル位置を維持するために、ファイル位置オフセットが更新されました。
なお、実際のドライバ開発では、デバイスの特性やカーネルのバージョン、要件に応じて適切に対応する必要があります。データの正確性とセキュリティ、およびメモリの合理的な管理を確保することは非常に重要です。
クラス作成
class_create 関数は、デバイス クラスの作成に使用される Linux カーネルの関数です。デバイス クラスは、関連するデバイスをグループ化して、これらのデバイスに共通のプロパティと操作を提供できるようにするデバイスを編成および管理する方法です。これは、キャラクターデバイスやブロックデバイスなどの管理に役立ちます。
struct class *class_create(struct module *owner, const char *name);
パラメータの説明:
owner
: このクラスを所有するカーネル モジュールを指定します。通常、これを THIS_MODULE に設定できます。これは、クラスを作成したモジュールが現在のモジュールであることを意味します。
name
: デバイス クラスの名前を指定します。この名前は /sys/class ディレクトリに表示され、デバイス クラスを識別するために使用されます。
戻り値:
成功すると、新しく作成されたデバイス クラスへのポインタを返します。
失敗すると、エラー ポインタが返されます。関数を
呼び出した後、関連するデバイスをこのクラスに登録すると、カーネルはこれらのデバイスに対応するデバイス ファイルを作成し、そのディレクトリの下に対応するディレクトリを作成します。class_create
/sys/class
次に、class_create 関数の使用方法を示す例を示します。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
static struct class *my_class;
static int __init my_module_init(void)
{
my_class = class_create(THIS_MODULE, "my_device_class");
if (IS_ERR(my_class)) {
printk(KERN_ALERT "Failed to create class\n");
return PTR_ERR(my_class);
}
// Create and register devices here
printk(KERN_INFO "Module initialized\n");
return 0;
}
static void __exit my_module_exit(void)
{
class_destroy(my_class);
printk(KERN_INFO "Module exited\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Class Module");
上記の例では、class_create
最初に関数 を使用して という名前のデバイス クラスが作成されます"my_device_class"
。その後、モジュールの初期化関数でデバイスを作成して登録することで、関連するデバイスをこのクラスに追加できます。モジュールが終了したら、class_destroy
関数を使用してデバイス クラスを破棄します。
class_create および関連するデバイス操作関数はカーネル モジュールの開発に使用されることに注意してください。キャラクター デバイス ドライバーまたは他のタイプのカーネル モジュールを開発している場合は、必要に応じてこれらの関数を使用してデバイス クラスとデバイスを管理できます。
デバイス作成
device_create
function は、Linux カーネルでデバイスを作成するために使用される関数で、デバイス クラスのサブディレクトリ/sys/class/
を作成し、そのサブディレクトリ内にデバイス ファイルを作成します。この関数は通常、class_create
デバイスを特定のデバイス クラスに関連付ける関数と組み合わせて使用されます。
device_create
これは関数のプロトタイプです。
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
パラメータの説明:
class
: デバイスが属するデバイス クラスを指定します。このパラメータは通常、class_create 関数によって返されるデバイス クラス ポインターです。
parent
: デバイスの親デバイスを指定します。通常は NULL に設定できます。
devt
: デバイスのデバイス番号を指定します。デバイス番号は、MKDEV (メジャー、マイナー) マクロを使用して作成できます。
drvdata
:デバイスのプライベートデータポインタを指定します。
fmt
: /sys/class/ の下にサブディレクトリとデバイス ファイルを作成するために使用されるデバイス名の形式を指定します。
戻り値:
成功すると、新しく作成されたデバイスへのポインタを返します。
失敗すると、エラー ポインタが返されます。
次に、device_create 関数の使用方法を示す例を示します。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
static struct class *my_class;
static struct device *my_device;
static int __init my_module_init(void)
{
my_class = class_create(THIS_MODULE, "my_device_class");
if (IS_ERR(my_class)) {
printk(KERN_ALERT "Failed to create class\n");
return PTR_ERR(my_class);
}
// Create and register devices here
my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
if (IS_ERR(my_device)) {
printk(KERN_ALERT "Failed to create device\n");
class_destroy(my_class);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Module initialized\n");
return 0;
}
static void __exit my_module_exit(void)
{
device_destroy(my_class, MKDEV(0, 0));
class_destroy(my_class);
printk(KERN_INFO "Module exited\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Module");
上の例では、まずclass_create
関数を使用してデバイス クラスを作成します。次に、device_create
そのクラスの下に名前を付けたデバイスが関数を使用して作成されました"my_device"
。モジュールの終了時に、device_destroy
関数を使用してデバイスを破棄し、次にclass_destroy
関数を使用してデバイス クラスを破棄します。
なお、device_create
関連するデバイス操作機能はカーネルモジュールの開発に使用されます。キャラクター デバイス ドライバーまたは他のタイプのカーネル モジュールを開発している場合は、これらの関数を使用して、必要に応じてデバイスを作成および管理できます。
イオレマップ
Linux カーネル開発では、ioremap
関数を使用して物理アドレスをカーネル空間にマップし、カーネルがこれらのアドレスにあるハードウェア レジスタ、周辺メモリなどに直接アクセスできるようにします。これは、ハードウェア レジスタへのアクセスには通常、特殊な I/O 命令の使用が必要であり、これらの命令はカーネル モードでのみ実行できるためです。
ioremap
関数のプロトタイプは次のとおりです。
void __iomem *ioremap(resource_size_t phys_addr, size_t size);
パラメータの説明:
phys_addr
: マッピングされる物理アドレス。
size
: マップのサイズ (バイト単位)。
戻り値:
成功すると、マップされた領域へのポインタを返します。
失敗すると、null ポインタが返されます。
この機能を使用する前にioremap
、自分の物理アドレスが有効であり、他人に使用されていないことを確認する必要があります。マッピング後は、通常のメモリにアクセスするのと同じように、カーネル内のポインタを使用して、マッピングされた領域の内容にアクセスできます。
以下は、ioremap 関数を使用してハードウェア レジスタにアクセスする方法を示す例です。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
static void __iomem *hw_regs;
static int __init my_module_init(void)
{
// 为物理地址0x12345678映射一个大小为4字节的映射区域
hw_regs = ioremap(0x12345678, 4);
if (!hw_regs) {
printk(KERN_ALERT "Failed to remap hardware registers\n");
return -ENOMEM;
}
// 使用映射后的指针来访问寄存器
unsigned int reg_value = readl(hw_regs);
printk(KERN_INFO "Hardware register value: %u\n", reg_value);
printk(KERN_INFO "Module initialized\n");
return 0;
}
static void __exit my_module_exit(void)
{
// 取消映射
iounmap(hw_regs);
printk(KERN_INFO "Module exited\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample I/O Remapping Module");
上記の例では、最初にioremap
関数を使用して0x12345678
物理アドレスをカーネル空間にマッピングし、次にreadl
関数を使用してマッピングされた領域からハードウェア レジスタの値を読み取ります。モジュールの終了時にiounmap
関数を使用してマップを解除します。
ioremap と関連関数は通常、低レベルのハードウェア プログラミングに使用されるため、マップされた領域にアクセスする際はハードウェア デバイスの仕様と要件に確実に従うように注意して使用する必要があることに注意してください。
MODULE_LICENSE(「GPL」);
MODULE_LICENSE(「GPL」);
MODULE_AUTHOR(「あなたの名前」);
MODULE_DESCRIPTION(“サンプル I/O リマッピング モジュール”);
MODULE_LICENSE("GPL");
:
このマクロは、カーネル モジュールのライセンス タイプを指定するために使用されます。Linux カーネル開発では、さまざまなライセンス タイプに従うことが重要です。「GPL」は GNU General Public License の略で、派生コードも GPL に準拠する必要があるオープン ソース ソフトウェア ライセンスです。モジュールが別のライセンスを使用する場合は、ここで「LGPL」、「MIT」などの他のライセンス タイプを指定できます。
MODULE_AUTHOR("Your Name");
:
このマクロは、カーネル モジュールの作成者情報を指定するために使用されます。「あなたの名前」を実際の名前またはロゴに置き換えてください。
MODULE_DESCRIPTION("Sample I/O Remapping Module");
:
このマクロは、カーネル モジュールの簡単な説明を提供するために使用されます。ここでは、モジュールの機能、目的、または特徴を説明できます。モジュールの説明は通常、モジュールがロードされるときに表示されます。
これらのマクロは、カーネル モジュールに関する情報を記録および表示する標準化された方法を提供し、開発者がモジュールの特性と背景を理解するのに役立ちます。他の開発者やカーネル保守者がコードを見ると、モジュールに関する基本情報を簡単に理解できます。
仕上げる
ご質問がございましたら、お気軽にお問い合わせください。一緒に前進してまいります。