https://blog.csdn.net/qq_52749711/article/details/132409329
都差不多,这里的名字被改变,万变不离其宗
与用户态的main函数一样,它是调用括号里面的函数来执行,里面的函数为入口函数
比如:
static int pin5_drv_init(void);//函数声明
module_init(pin5_drv_init)
函数里面包含了,注册驱动等代码
运行完之后需要执行的函数,原理同上
module_exit(pin5_drv_exit);
函数包含了,卸载驱动等代码
register_chrdev
函数是Linux内核中用于注册字符设备驱动程序的函数之一。字符设备是一种与字符流进行交互的设备,例如终端、串口等。在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
#include
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
是字符设备驱动中的一个函数,用于处理设备的打开操作。在注册字符设备时,你需要在struct file_operations
结构体中设置一个指向你自己实现的open函数的指针,内核在设备被打开时会调用这个函数。
这是my_chardev_open函数的定义:
static int my_chardev_open(struct inode *inode, struct file *file)
{
// Open operation implementation
return 0;
}
在这个函数中,你可以执行与设备打开操作相关的任务,比如初始化设备状态、分配资源、记录设备打开次数等。这个函数会接收两个参数:
struct inode *inode
:这是一个指向文件的索引节点(inode)的指针,包含了有关文件的元数据信息。在设备打开时,内核会将相关的inode传递给打开函数。
struct inode *inode:
inode
(索引节点)包含了文件的元数据信息,如文件的权限、大小、所属用户等。它在文件系统中唯一标识一个文件。
你可以使用i_mode
字段来获取文件的权限信息,使用i_uid
和i_gid
字段来获取文件的用户和组ID。
i_private
字段可以用于存储与设备有关的私有数据,你可以在驱动初始化时设置该字段。
struct file *file
:这是一个指向表示文件的数据结构的指针。它包含了与文件操作相关的信息,如访问模式、文件位置等。
struct file *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
: 这是表示打开的文件的struct 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
#include
// 假设你的设备在打开时已经被初始化为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内核中用于创建设备类(device class)的函数。设备类是一种组织和管理设备的方式,它允许将相关设备分组在一起,并为这些设备提供共同的属性和操作。这对于字符设备、块设备等的管理非常有用。
struct class *class_create(struct module *owner, const char *name);
参数说明:
owner
:指定拥有该类的内核模块。一般情况下,你可以将它设置为THIS_MODULE,表示创建类的模块就是当前模块。
name
:指定设备类的名称,这个名称会显示在/sys/class目录下,用于标识设备类。
返回值:
成功时,返回指向新创建的设备类的指针。
失败时,返回一个错误指针。
在调用class_create
函数后,你可以将相关设备注册到这个类中,然后内核会为这些设备创建对应的设备文件,并在/sys/class
目录下创建相应的目录。
以下是一个示例,展示了如何使用class_create函数:
#include
#include
#include
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
函数是用于在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(major, minor)宏来创建设备号。
drvdata
:指定设备的私有数据指针。
fmt
:指定设备名称的格式,用于在/sys/class/下创建子目录和设备文件。
返回值:
成功时,返回指向新创建的设备的指针。
失败时,返回一个错误指针。
以下是一个示例,展示了如何使用device_create函数:
#include
#include
#include
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
:映射的大小,以字节为单位。
返回值:
成功时,返回一个指向映射区域的指针。
失败时,返回一个空指针。
在使用ioremap
函数之前,你需要确保你的物理地址是有效的,且没有被其他部分使用。在映射后,你可以在内核中使用指针来访问映射区域的内容,就像访问普通的内存一样。
以下是一个示例,展示了如何使用ioremap函数来访问硬件寄存器
#include
#include
#include
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_AUTHOR(“Your Name”);
MODULE_DESCRIPTION(“Sample I/O Remapping Module”);
MODULE_LICENSE("GPL");
:
这个宏用于指定内核模块的许可证类型。在Linux内核开发中,遵循不同的许可证类型是很重要的。“GPL"代表GNU General Public License,这是一种开源软件许可证,要求派生的代码也必须遵循GPL。如果模块使用不同的许可证,可以在这里指定其他许可证类型,如"LGPL”、"MIT"等。
MODULE_AUTHOR("Your Name");
:
这个宏用于指定内核模块的作者信息。你应该将"Your Name"替换为你的真实姓名或标识。
MODULE_DESCRIPTION("Sample I/O Remapping Module");
:
这个宏用于提供内核模块的简要描述。在这里,你可以描述模块的功能、用途或特点。模块的描述信息通常会在加载模块时显示。
这些宏提供了一种标准化的方式来记录和显示有关内核模块的信息,有助于开发人员了解模块的特性和背景。当其他开发人员或内核维护者查看你的代码时,他们可以轻松地了解模块的基本信息。
如有问题欢迎提出,共同进步。