1. 書き込みとデバッグ
(1) 実験1
Linux デバイス ドライバーは 2 つの形式で定義できます。1 つはグローバル静的変数、もう 1 つはカーネルによって提供される API を使用する方法です。ここでは 2 番目の方法を使用して、単純な仮想デバイス ドライバーを実装し、それを実装する関数の読み取りと書き込みを行います。
カーネル モード コード device_drive.c:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/cdev.h>
#define DEMO_NAME "my_demo_dev"
static dev_t dev; //设备号,dev_t封装的是u32类型,该类型前12位表示主设备号,后20位表示次设备号
static struct cdev *demo_cdev; //字符设备
static signed count = 1;
//在demodrv_open操作中打印主次设备号
static int demodrv_open(struct inode *inode, struct file *file)
{
//Linux内核提供的读取主设备号和次设备号的方法
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__,major,minor); //__func__宏获取当前的函数名
return 0;
}
//在demodrv_read和demodrv_write中仅打印函数名
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos)
{
printk("%s enter\n",__func__); //打印函数名
return 0;
}
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos)
{
printk("%s enter\n",__func__);
return 0;
}
//给设备的操作,和在文件系统中使用的是相同的结构体
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write
};
static int __init simple_char_init(void)
{
int ret;
ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME); //动态申请设备号
if(ret)
{
printk("failed to allocate char device region\n");
return ret;
}
demo_cdev = cdev_alloc(); //分配空间
if(!demo_cdev)
{
printk("cdev_alloc failed\n");
goto unregister_chrdev;
}
cdev_init(demo_cdev,&demodrv_fops); //cdev_init初始化cdev,如果定义的是struct cdev结构体而不是指针类型,只需要执行cdev_init()就可以了
ret = cdev_add(demo_cdev,dev,count); //把这个设备添加到系统中
if(ret)
{
printk("cdev_add failed\n");
goto cdev_fail;
}
printk("successed register char device: %s\n",DEMO_NAME);
printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));
return 0;
cdev_fail:
cdev_del(demo_cdev);
unregister_chrdev:
unregister_chrdev_region(dev,count);
return ret;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
if(demo_cdev)
cdev_del(demo_cdev);
unregister_chrdev_region(dev,count);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL");
操作結果:
さらに、生成されたデバイスは、/dev ディレクトリに対応するノードを生成する必要があります。手動で生成する必要があります。mknod コマンドを使用し、コマンド sudo mknod /dev/demo_drv c 240 0 を実行します。c はキャラクター デバイス、240 を表しますはメジャー デバイス番号、0 はこのデバイス番号です。次に、コマンド ll /dev を実行して、dev ディレクトリのステータスを表示します。
次に、ユーザー空間テスト プログラムを使用して、このキャラクター デバイス ドライバーをテストします。
test.c コードは次のとおりです。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define DEMO_DEV_NAME "/dev/demo_drv" //定义设备的路径
int main()
{
char buffer[64];
int fd;
fd = open(DEMO_DEV_NAME,O_RDONLY); //O_RDONLY: 以只读的方式打开
if(fd<0)
{
printf("open device %s failed\n",DEMO_DEV_NAME);
return -1;
}
read(fd,buffer,64);
close(fd);
return 0;
}
テスト プログラムを実行し、カーネル メッセージを出力します。
open メソッドと read メソッドを出力する
(2) 実験2
キャラクター デバイス ドライバーは、misc メカニズムを使用して登録することもできます。つまり、Linux では、所定の要件を満たさない一部のキャラクター デバイスがその他のデバイスに分割されます。そのようなデバイスのメジャー デバイス番号は 10 です。miscdevice 構造体は、カーネルで使用されて、 misc メカニズムを使用してデバイスを作成するには、miscdevice 構造体を定義する必要があります。
カーネルモジュール drive2.c:
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
//加入misc机制
# include <linux/miscdevice.h>
# include <linux/kfifo.h>
DEFINE_KFIFO(mydemo_fifo,char,64); //定义和初始化一个fifo
//设备名
# define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__,major,minor);
return 0;
}
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
int actual_readed;
int ret;
ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed);
if(ret)
return -EIO;
printk("%s,actual_readed=%d,pos=%lld\n",__func__,actual_readed,*ppos);
return actual_readed;
}
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos)
{
unsigned int actual_write;
int ret;
ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write);
if(ret)
return -EIO;
printk("%s: actual_write=%d,ppos=%lld\n",__func__,actual_write,*ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write,
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops, //设备相应的操作
};
static int __init simple_char_init(void)
{
int ret;
ret = misc_register(&mydemodrv_misc_device); //misc_register()函数来注册,可以自动创建设备结点,不需要mknod来手动创建设备节点,传入的参数是定义的miscdevice结构体的地址
if(ret)
{
printk("failed register misc device\n");
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("successed register char device: %s\n",DEMO_NAME);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL");
操作結果:
dev ディレクトリを表示します。
ユーザーモードテストプログラムtest.c:
# include <stdio.h>
# include <fcntl.h>
# include <unistd.h>
# include <malloc.h>
# include <string.h>
# define DEMO_DEV_NAME "/dev/my_demo_dev" //设备路径
int main()
{
char buffer[64];
int fd;
int ret;
size_t len;
char message[] = "hello";
char *read_buffer;
len = sizeof(message);
fd = open(DEMO_DEV_NAME,O_RDWR);
if(fd<0)
{
printf("open device %s failed\n",DEMO_DEV_NAME);
return -1;
}
ret = write(fd,message,len); //向设备写数据
if(ret != len)
{
printf("cannot write on device %d,ret=%d\n",fd,ret);
return -1;
}
read_buffer = malloc(2*len);
memset(read_buffer,0,2*len); //memset是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。
ret = read(fd,read_buffer,2*len); //把数据从设备读到read_buffer
printf("read %d bytes\n",ret);
printf("read buffer=%s\n",read_buffer);
close(fd);
return 0;
}
このテスト プログラムを実行します。
カーネルメッセージを出力します。
2. ソースコードの学習
//在linux内核中,字符设备是由cdev结构体来描述的
struct cdev {
struct kobject kobj;
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作结构体
struct list_head list; //list字段将所有的字符设备组织成一个链表
dev_t dev; //dev是字符设备的设备号,包括主设备号和次设备号
unsigned int count; //count字段是同一个主设备号中次设备号的个数
} __randomize_layout;
#define MINORBITS 20 //次设备号的位数是20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //读取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //读取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //生成设备号
static struct char_device_struct {
struct char_device_struct *next; //next成员实现这些结构体之间的关联,在系统中主要是完成该类型变量的链接
unsigned int major; //major表示字符设备的主设备号
unsigned int baseminor; //baseminor表示起始次设备号
int minorct; //minorct表示该字符设备支持的最大次设备个数
char name[64];
struct cdev *cdev; /* will die */ //cdev指针指向该字符设备对应的设备抽象结构体变量
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
char_device_struct は、登録されたキャラクターデバイス間隔の管理を実装し、cdev との関連付けを完了します。
cdevは主に、cdev の参照カウントと sysfs との関連付けを実装するために使用されます。
3. Q&A
1. デバイスドライバーとファイルシステムをそれぞれ説明します。
デバイス ドライバーは、シリンダー、トラック、セクターなどの 3 次元データを 1 次元の線形アドレス空間のブロックに変換する最初の抽象レベルです。
ファイル システムは、第 1 レベルの抽象化に基づく第 2 レベルの抽象化です。つまり、ブロックが抽象化され、ファイル システムに編成されます。
2. アプリケーションの open()、read()、および write() システム コールから始めて、カーネルにトラップされる方法を説明します。
3.file_operations() 構造は何をするのですか?
file_operations 構造体は、デバイスの読み取り、書き込み、保存などを完了します。これらの操作はすべて、file_operations 構造体に格納されている関数ポインターによって処理されます。これらの関数ポインターが指す関数は、ドライバー モジュールに実装する必要があります。
struct file_operations {
struct module *owner; //拥有该结构的模块的指针,一般为THIS_MODULES
......
int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
......
int (*open) (struct inode *, struct file *); //打开
......
}
4. アプリケーションの open()、read()、write() から file_operations() 内の対応する関数の呼び出しまで、次の図についての理解を教えてください。
5.デバイス番号はドライバー内でどのような役割を果たしますか? なぜメジャーデバイス番号とマイナーデバイス番号が必要なのでしょうか?
各ファイルはデバイスであり、デバイス番号はメジャーデバイス番号とマイナーデバイス番号で構成されます。
メジャーデバイス番号:
- 一般に、メジャー デバイス番号がドライバーに対応すると考えられています。
- 1 つのドライバーで複数のデバイスを管理できます。つまり、1 つのメジャー デバイス番号が複数のデバイスに対応します。
- 1 つのメジャー デバイス番号が複数のドライバーに対応することもできます
マイナーデバイス番号:
- マイナーデバイス番号はデバイスに対応します
この種のデバイスは 1 つのドライバーで複数管理でき、マイナーデバイス番号は 20 桁であるため、デバイスの数は 2 の 20 乗まで可能ですが、実際にはこれほど多くのデバイスを管理することは不可能です。
6. ドライバーの登録および登録解除機能は何ですか? なぜ登録とキャンセルが必要なのでしょうか?
キャラクターデバイスドライバーの登録関数:
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
/*__register_chrdev() - 创建并注册一个占有较小大小的字符设备
major:主设备号,当用户设置为0时,内核会动态分配一个设备号。
baseminor: 次设备号,要在一定范围内从0开始
count: 次设备号的范围
name: 设备名称
fops: 文件系统的接口指针
如果major == 0,此函数将动态分配一个major并返回它的编号。
如果major > 0,此函数将尝试使用给定的主设备号来保留设备,成功时将返回0。
失败时返回-ve errno。
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name); //注册一个主设备号和一个一定范围内具体的次设备号
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc(); //分配空间
if (!cdev)
goto out2;
//对cdev进行赋值操作
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); //把这个设备添加到系统中
if (err)
goto out;
cd->cdev = cdev; //完成cd与cdev的关联
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
キャラクターデバイスドライバーの関数の登録を解除します。
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name)
{
struct char_device_struct *cd;
cd = __unregister_chrdev_region(major, baseminor, count);
if (cd && cd->cdev)
cdev_del(cd->cdev);
kfree(cd);
}
登録とは、使用する対応するデバイスドライバーを生成することです。
ログオフするとメモリが解放され、他のプロセスが実行できるようになります。