编写一个简单的驱动程序

1、确定主设备号

static int major = 0;

2、定义一个file_operations结构体

static struct file_operations my_drv = {
	.owner   = THIS_MODULE,
	.open    = my_drv_open,
	.read    = my_drv_read,
	.write   = my_drv_write,
	.release = my_drv_close,
};

其中,file_operations 是Linux内核中用于字符设备驱动程序的结构体,驱动程序注册时,需要将file_operations结构体的指针传递给内核,以便内核在需要时调用相应的操作函数。
这样,用户空间的应用程序就可以通过文件操作接口与设备驱动程序进行交互它定义了字符设备驱动程序的操作函数,这些函数用于处理对字符设备的不同操作,如打开、关闭、读取、写入等。
结构体定义在<linux/fs.h>头文件中,其定义如下:

struct file_operations {
   //一个指向拥有该结构体的模块的指针
    struct module *owner;
    
    //用于实现文件指针的定位操作(如lseek系统调用)
    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 *);
    
    //用于打开设备文件时的操作
    int (*open) (struct inode *, struct file *);
    
    //用于关闭设备文件时的操作
    int (*release) (struct inode *, struct file *);
    // 其他函数指针...
};

3、分别实现上述的my_drv_open/read/write/close函数

#define MIN(a, b) (a < b ? a : b)
static ssize_t my_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offeset){
  int err;
  printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
  /*
    unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
    是一个用于将内核空间数据拷贝到用户空间的函数。
    它是在Linux内核中提供的一个函数,用于在内核态中将数据从内核空间复制到用户空间。
    参数声明:
        (1)to   :指向用户空间的目标缓冲区的指针
        (2)from :指向内核空间源缓冲区的指针
        (3)n    :要拷贝的字节数
    函数返回值:
        成功复制的字节数
  */
  err = copy_to_user(buf,kernel_buf,MIN(1024,size));
  return err;
}
static ssize_t my_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__);
	
	/*
	   unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
	   是Linux内核中的一个函数,用于将用户空间的数据复制到内核空间
       参数说明:
          to   :目标缓冲区的指针,表示要将数据复制到的内核空间地址
          from :源缓冲区的指针,表示要从用户空间复制数据的地址
          n    :要复制的字节数
       返回值:
        返回未复制成功的字节数,如果返回0表示全部复制成功。
	*/
	err = copy_from_user(kernel_buf,buf,MIN(1024,size));
	return err;
  
}
static int my_drv_open(struct inode *node, struct file *file){
	
	printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}

static int my_drv_close(struct inode *node, struct file *file){
	printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}

4、注册驱动程序(驱动入口函数)

static int __init mydrv_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    /*
     int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
     该函数是Linux内核中的一个函数,用于注册字符设备驱动程序。
     参数说明:
		major:主设备号,用于唯一标识一个字符设备驱动程序,
		       如果传入0,则表示由内核动态分配主设备号。
		name:设备名称,用于在/dev目录下创建设备文件。
		fops:指向struct file_operations的指针,表示字符设备驱动程序提供的操作函数。
	  返回值:
		返回0表示注册成功,负值表示注册失败
		
	  register_chrdev函数用于注册一个字符设备驱动程序,
	  将其与主设备号、设备名称和操作函数关联起来。
	  注册成功后,内核会分配一个主设备号,并在/dev目录下创建相应的设备文件(名称由name参数指定)。
	  当用户打开设备文件并进行读写操作时,内核会调用注册的操作函数来处理相应的操作。
   */
	major = register_chrdev(0, "mydrv", &my_drv);  /* /dev/mydrv*/

    /*
      struct class *class_create(struct module *owner, const char *name)
      Linux内核中的一个函数,用于创建一个设备类
      参数说明:
         owner:指向拥有该设备类的内核模块的指针。通常使用THIS_MODULE宏表示当前模块。
         name:设备类的名称,用于在/sys/class目录下创建相应的类目录。
      返回值:
         返回一个指向struct class的指针,表示创建的设备类。如果创建失败,返回NULL。
         
      class_create函数用于在内核中创建一个设备类,设备类是一组具有相似特性的设备的集合。
      通过创建设备类,可以将一组设备归类并在/sys/class目录下创建相应的类目录,方便用户空 
      间和内核空间进行设备管理。

      注意,class_create函数通常与device_create函数一起使用,
      后者用于在设备类下创建具体的设备对象。
      创建设备对象后,可以在/dev目录下看到相应的设备文件,方便用户空间与设备进行交互。

   */
	mydrv_class = class_create(THIS_MODULE, "mydrv_class");// /sys/class/mydrv_class
	err = PTR_ERR(mydrv_class);
	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "mydrv");
		return err;
	}
	
	/*
     struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
     该函数用于在指定的设备类下创建一个设备对象
     参数说明:
		class:指向要创建设备对象的设备类的指针。
		parent:指向父设备的指针,如果没有父设备,可以传入NULL。
		devt:设备号,表示要创建的设备对象的设备号。
		drvdata:设备对象关联的私有数据,可以是任意类型的指针。
		fmt:设备对象的名称格式字符串,用于在/sys/class/<class_name>目录下创建相应的设   备目录。
     返回值:
        返回一个指向struct device的指针,表示创建的设备对象。如果创建失败,返回NULL
   */
	device_create(mydrv_class, NULL, MKDEV(major, 0), NULL, "mydrv");  
	// sys/class/mydrv_class/mydrv
	
	return 0;
}

6、出口函数(卸载驱动程序)

static void __exit mydrv_exit(void){
	printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
	
	/*
      void device_destroy(struct class *class, dev_t devt)
      用于销毁一个设备对象;
      参数说明:
		  class:指向设备对象所属的设备类的指针;
		  devt:设备号,表示要销毁的设备对象的设备号;
		  
      device_destroy函数用于销毁指定设备类下的指定设备对象。它会释放设备对象占用的资源,
      并从内核中移除该设备对象。
      同时,相关的设备文件和/sys/class/<class_name>目录中的设备目录也会被删除;
      在调用device_destroy函数之前,通常需要确保设备对象已经停止使用,
      并释放与之相关的资源。销毁设备对象应该在不再需要该设备对象的时候进行,
      以避免资源泄漏和冲突;
   */
	device_destroy(mydrv_class,MKDEV(major,0));
   
    /*
      void class_destroy(struct class *cls)
      用于销毁字符设备类(struct class)。它的原型在头文件<linux/device.h>中定义。
      该函数接收一个指向struct class类型的指针作为参数,该指针指向要销毁的字符设备类。
      该函数会释放与字符设备类相关的资源,并将其从系统中移除。
   */
	class_destroy(mydrv_class);
	
   /*
      void unregister_chrdev(unsigned int major, const char *name)
      用于注销已经注册的字符设备驱动程序;
      参数说明:
          major:要注销的字符设备驱动程序的主设备号;
          name:要注销的字符设备驱动程序的设备名称;
   */
	unregister_chrdev(major,"mydrv");
}

7、提供设备信息,自动创建设备节点

module_init(mydrv_init);
module_exit(mydrv_exit);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你解答这个问题。字符设备驱动程序是Linux内核中的一种驱动程序类型,它负责管理字符设备(如串口),使得应用程序可以通过文件IO的方式对设备进行读写操作。下面是一个简单的字符设备驱动程序的框架代码: ```c #include <linux/module.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/cdev.h> MODULE_LICENSE("Dual BSD/GPL"); #define DEVICE_NAME "mydevice" #define CLASS_NAME "mydevice_class" #define BUFF_SIZE 1024 static int major; static struct class *myclass = NULL; static struct device *mydevice = NULL; static struct cdev c_dev; static int device_open(struct inode *i, struct file *f) { // 在应用程序打开设备文件时调用,可以进行初始化等操作。 // 如果设备被占用,可以返回 -EBUSY。 return 0; } static int device_release(struct inode *i, struct file *f) { // 在应用程序关闭设备文件时调用,可以进行清理等操作。 return 0; } static ssize_t device_read(struct file *f, char __user *buff, size_t len, loff_t *off) { // 在应用程序读取设备文件时调用,把设备数据读取到 buff 中。 // 返回值是读取到的字节数,如果已经到文件末尾返回 0。 return 0; } static ssize_t device_write(struct file *f, const char __user *buff, size_t len, loff_t *off) { // 在应用程序向设备文件中写入数据时调用,把 buff 中的数据写入到设备中。 // 返回值是写入的字节数。 return 0; } static struct file_operations f_ops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .read = device_read, .write = device_write }; static int __init mydevice_init(void) { // 注册字符设备驱动程序,在加载模块时调用。 // 这里我们分配一个 cdev 结构体,并注册到内核中。 int ret = alloc_chrdev_region(&major, 0, 1, DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to allocate device number\n"); return ret; } myclass = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(myclass)) { unregister_chrdev_region(major, 1); return PTR_ERR(myclass); } mydevice = device_create(myclass, NULL, major, NULL, DEVICE_NAME); if (IS_ERR(mydevice)) { class_destroy(myclass); unregister_chrdev_region(major, 1); return PTR_ERR(mydevice); } cdev_init(&c_dev, &f_ops); ret = cdev_add(&c_dev, major, 1); if (ret < 0) { device_destroy(myclass, major); class_destroy(myclass); unregister_chrdev_region(major, 1); return ret; } printk(KERN_INFO "Registered a new device with major number %d\n", major); return 0; } static void __exit mydevice_exit(void) { // 卸载字符设备驱动程序,在卸载模块时调用。 cdev_del(&c_dev); device_destroy(myclass, major); class_destroy(myclass); unregister_chrdev_region(major, 1); printk(KERN_INFO "Unregistered the device\n"); } module_init(mydevice_init); module_exit(mydevice_exit); ``` 这段代码实现了一个名为 `mydevice` 的字符设备驱动程序,提供了 `open`、`release`、`read` 和 `write` 四个驱动接口函数。在加载模块时,会为该设备分配一个主设备号,然后创建设备结点,并将驱动接口函数注册到内核中。在卸载模块时,会删除设备结点以及注销驱动接口函数。 当然,这仅仅是一个最基础的示例,真正的字符设备驱动还需要对设备进行一系列的初始化、卸载、IO操作等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值