当前位置:   article > 正文

【Linux 驱动基础】第一个驱动

【Linux 驱动基础】第一个驱动

# 前置知识

        APP 打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP的每一个文件句柄,在内核里面都有一个“ struct file”与之对应。

  • 使用open函数时,用户态调用 API 触发异常进入内核
  • 内核识别异常后,取出异常值,从而调用sys_call_table[__NR_open],sys_call_table就是一个数组,其值为typedef void (*sys_call_ptr_t)(void)类型的函数指针,内容大致如下:
  1. __SYSCALL_COMMON(0, sys_read, sys_read)
  2. __SYSCALL_COMMON(1, sys_write, sys_write)
  3. __SYSCALL_COMMON(2, sys_open, sys_open)
  4. __SYSCALL_COMMON(3, sys_close, sys_close)
  5. __SYSCALL_COMMON(5, sys_newfstat, sys_newfstat)
  6. ...
  7. ...
  8. ...
  •  最后调用sys_open函数,sys_open函数定义如下:
  1. SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
  2. {
  3. long ret;
  4. if (force_o_largefile())
  5. flags |= O_LARGEFILE;
  6. ret = do_sys_open(AT_FDCWD, filename, flags, mode);
  7. /* avoid REGPARM breakage on x86: */
  8. asmlinkage_protect(3, ret, filename, flags, mode);
  9. return ret;
  10. }
  • 在sys_open函数里面,又调用了 de_sys_open 函数,定义如下:
  1. long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
  2. {
  3. char *tmp = getname(filename);
  4. int fd = PTR_ERR(tmp);
  5. if (!IS_ERR(tmp)) {
  6. fd = get_unused_fd_flags(flags); /* 获取未使用的函数句柄 */
  7. if (fd >= 0) {
  8. struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); /* 打开文件,得到file结构体 */
  9. if (IS_ERR(f)) {
  10. put_unused_fd(fd);
  11. fd = PTR_ERR(f);
  12. } else {
  13. fsnotify_open(f);
  14. fd_install(fd, f); /* 在当前进程里面记录下来 */
  15. }
  16. }
  17. putname(tmp);
  18. }
  19. return fd;
  20. }
  • 通过以上过程,就绑定了文件句柄fd与file结构体,后续操作文件句柄fd,实际上就是操作文件结构体file,文件结构体file如下,在结构体中,有f_op,里面包含了读写等对文件的操作;有f_flags,这就是使用open函数时的打开模式;有f_pos,这就是使用读写函数时,指向文件的位置,整体情况如下图:

 # 驱动编写流程

         从上述可以得出,当我们使用openwritereadclose等函数操作驱动时,最终调用的是最底层file_operations结构体中的openwritereadclose函数。那么将这些函数完善,并且上报到系统,就是编写驱动程序。

  1. #include <linux/module.h>
  2. #include <asm/uaccess.h>
  3. #include <linux/fs.h>
  4. #include <linux/errno.h>
  5. #include <linux/miscdevice.h>
  6. #include <linux/kernel.h>
  7. #include <linux/major.h>
  8. #include <linux/mutex.h>
  9. #include <linux/proc_fs.h>
  10. #include <linux/seq_file.h>
  11. #include <linux/stat.h>
  12. #include <linux/init.h>
  13. #include <linux/device.h>
  14. #include <linux/tty.h>
  15. #include <linux/kmod.h>
  16. #include <linux/gfp.h>
  17. #define __CHRDEV_REGION_
  18. #define MIN(a, b) (a < b ? a : b)
  19. #define DEVICE_DRIVER_NAME "test_drv" /* 设备驱动名称 */
  20. #define DEVICE_DRIVER_CNT 1 /* 设备号个数 */
  21. static char kernal_buffer[1024];
  22. typedef struct {
  23. dev_t devid; /* 设备号 */
  24. int major; /* 主设备号 */
  25. int minor; /* 次设备号 */
  26. struct cdev cdev; /* cdev */
  27. char class_name[20]; /* 类名称 */
  28. struct class *class; /* 类 */
  29. struct device *device; /* 设备 */
  30. }test_drv_Typedef;
  31. static test_drv_Typedef test_driver = {
  32. .devid = 0,
  33. .major = 0,
  34. .minor = 0,
  35. .class_name = "test_class",
  36. .class = NULL,
  37. .device = NULL
  38. };
  39. static int test_drv_open(struct inode *node, struct file *fle)
  40. {
  41. printk("test_drv_open\r\n");
  42. return 0;
  43. }
  44. static int test_drv_release(struct inode *node, struct file *fle)
  45. {
  46. printk("test_drv_open\r\n");
  47. return 0;
  48. }
  49. static ssize_t test_drv_write(struct file *fle, const char __user *buf, size_t size, loff_t *offest)
  50. {
  51. ssize_t err;
  52. printk("test_drv_write\r\n");
  53. err = copy_from_user(kernal_buffer, buf, MIN(sizeof(kernal_buffer), size));
  54. return err;
  55. }
  56. static ssize_t test_drv_read(struct file *fle, char __user *buf, size_t size, loff_t *offest)
  57. {
  58. ssize_t err;
  59. printk("test_drv_read\r\n");
  60. err = copy_to_user(buf, kernal_buffer, MIN(sizeof(kernal_buffer), size));
  61. return err;
  62. }
  63. /* 2. 编写 file_operations 结构体 */
  64. static const struct file_operations test_drv_op = {
  65. .owner = THIS_MODULE,
  66. .open = test_drv_open,
  67. .release = test_drv_release,
  68. .write = test_drv_write,
  69. .read = test_drv_read,
  70. };
  71. /* 3. 编写字符设备驱动模块加载 */
  72. /* 3.1 创建设备号、注册字符设备驱动 */
  73. /* 3.2 创建一个class类型的对象 */
  74. /* 3.3 生成设备节点并导出到用户空间 */
  75. static int __init test_drv_init(void)
  76. {
  77. int ret = 0;
  78. printk("test_drv_init\r\n");
  79. /* 创建设备号 */
  80. #ifndef __CHRDEV_REGION_
  81. /* 静态和动态注册两种方法,主要是通过判断给定的主设备号是否为0来进行区别 */
  82. test_driver.major = register_chrdev(0, DEVICE_DRIVER_NAME, &test_drv_op);
  83. test_driver.devid = MKDEV(test_driver.major, test_driver.minor);
  84. #else
  85. if(test_driver.major) /* 静态注册 */
  86. {
  87. test_driver.devid = MKDEV(test_driver.major, test_driver.minor);
  88. ret = register_chrdev_region(test_driver.devid, DEVICE_DRIVER_CNT, DEVICE_DRIVER_NAME);
  89. if(ret < 0)
  90. {
  91. printk("register_chrdev_region fail\r\n");
  92. goto _err_out_regchrdev;
  93. }
  94. }
  95. else /* 动态注册 */
  96. {
  97. ret = alloc_chrdev_region(&test_driver.devid, 0, DEVICE_DRIVER_CNT, DEVICE_DRIVER_NAME);
  98. if(ret < 0)
  99. {
  100. printk("alloc_chrdev_region fail\r\n");
  101. goto _err_out_regchrdev;
  102. }
  103. test_driver.major = MAJOR(test_driver.devid);
  104. test_driver.minor = MINOR(test_driver.devid);
  105. }
  106. /* 初始化cdev */
  107. test_driver.cdev.owner = THIS_MODULE;
  108. cdev_init(&test_driver.cdev, &test_drv_op);
  109. /* 添加一个cdev */
  110. cdev_add(&test_driver.cdev, test_driver.devid, DEVICE_DRIVER_CNT);
  111. #endif
  112. test_driver.class = class_create(THIS_MODULE, test_driver.class_name);
  113. if (IS_ERR(test_driver.class))
  114. {
  115. printk("class_create fail\r\n");
  116. ret = PTR_ERR(test_driver.class);
  117. goto _err_out_class_crate;
  118. }
  119. test_driver.device = device_create(test_driver.class, NULL, test_driver.devid, NULL, DEVICE_DRIVER_NAME);
  120. if(IS_ERR(test_driver.device))
  121. {
  122. printk("device_create fail\r\n");
  123. ret = PTR_ERR(test_driver.device);
  124. goto _err_out_device_create;
  125. }
  126. return 0;
  127. _err_out_device_create:
  128. class_destroy(test_driver.class);
  129. _err_out_class_crate:
  130. #ifndef __CHRDEV_REGION_
  131. unregister_chrdev(test_driver.devid, DEVICE_DRIVER_NAME);
  132. #else
  133. unregister_chrdev_region(test_driver.devid, DEVICE_DRIVER_CNT);
  134. cdev_del(&test_driver.cdev);
  135. _err_out_regchrdev:
  136. #endif
  137. return ret;
  138. }
  139. /* 4. 编写字符设备驱动模块卸载*/
  140. /* 4.1 摧毁设备 */
  141. /* 4.2 摧毁类 */
  142. /* 4.3 卸载字符设备驱动 */
  143. static void __exit test_drv_exit(void)
  144. {
  145. printk("test_drv_exit\r\n");
  146. device_destroy(test_driver.class, test_driver.devid);
  147. class_destroy(test_driver.class);
  148. #ifndef __CHRDEV_REGION_
  149. unregister_chrdev(test_driver.major, DEVICE_DRIVER_NAME);
  150. #else
  151. unregister_chrdev_region(test_driver.devid, DEVICE_DRIVER_CNT);
  152. cdev_del(&test_driver.cdev);
  153. #endif
  154. }
  155. /* 5. 指定驱动出入口 */
  156. module_init(test_drv_init);
  157. module_exit(test_drv_exit);
  158. /* 6. 完善驱动信息 */
  159. MODULE_LICENSE("GPL"); //添加模块 LICENSE 信息
  160. MODULE_AUTHOR("JYW"); //添加模块作者信息

步骤1:确定主设备号和次设备号

        设备号的组成:

        为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,Linux 提供了一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面,定义如下:

  1. typedef __u32 __kernel_dev_t;
  2. typedef __kernel_dev_t dev_t;

        dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号, 低 20 位为次设备号

        设备号的操作函数: 

  1. /* 从 dev_t 中获取主设备号 */
  2. #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
  3. /* 从 dev_t 中获取次设备号 */
  4. #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
  5. /* 给定的主设备号和次设备号的值组合成 dev_t 类型的设备号 */
  6. #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

步骤2:编写 file_operations 结构体

        file_operations 的结构体就是 Linux 内核驱动操作函数集合,内容如下所示:

  1. struct file_operations {
  2. struct module *owner; /* 拥有该结构体的模块的指针,一般设置为 THIS_MODULE */
  3. loff_t (*llseek) (struct file *, loff_t, int); /* 修改文件当前的读写位置 */
  4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); /* 读取设备文件 */
  5. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 向设备文件写入(发送)数据 */
  6. ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
  7. ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
  8. int (*iterate) (struct file *, struct dir_context *);
  9. unsigned int (*poll) (struct file *, struct poll_table_struct *); /* 轮询函数,用于查询设备是否可以进行非阻塞的读写 */
  10. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /* 提供对于设备的控制功能,与应用程序中的 ioctl 函数对应 */
  11. long (*compat_ioctl) (struct file *, unsigned int, unsigned long); /* 与unlocked_ioctl 函数功能一样 */
  12. int (*mmap) (struct file *, struct vm_area_struct *); /* 将设备的内存映射到进程空间中(也就是用户空间) */
  13. int (*mremap)(struct file *, struct vm_area_struct *);
  14. int (*open) (struct inode *, struct file *); /* 打开设备文件 */
  15. int (*flush) (struct file *, fl_owner_t id);
  16. int (*release) (struct inode *, struct file *); /* 释放(关闭)设备文件 */
  17. int (*fsync) (struct file *, loff_t, loff_t, int datasync); /* 刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中 */
  18. int (*aio_fsync) (struct kiocb *, int datasync); /* 与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据 */
  19. int (*fasync) (int, struct file *, int);
  20. int (*lock) (struct file *, int, struct file_lock *);
  21. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  22. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  23. int (*check_flags)(int);
  24. int (*flock) (struct file *, int, struct file_lock *);
  25. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  26. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  27. int (*setlease)(struct file *, long, struct file_lock **, void **);
  28. long (*fallocate)(struct file *file, int mode, loff_t offset,
  29. loff_t len);
  30. void (*show_fdinfo)(struct seq_file *m, struct file *f);
  31. #ifndef CONFIG_MMU
  32. unsigned (*mmap_capabilities)(struct file *);
  33. #endif
  34. };

        常用函数介绍:

  1. /*
  2. * @description : 打开设备
  3. * @param – : 传递给驱动的 inode
  4. * @param - : 设备文件, file 结构体有个叫做 private_data 的成员变量
  5. * 一般在 open 的时候将 private_data 指向设备结构体。
  6. * @return : 0 成功;其他 失败
  7. */
  8. int (*open) (struct inode *, struct file *);
  1. /*
  2. * @description : 关闭/释放设备
  3. * @param - : 要关闭的设备文件(文件描述符)
  4. * @return : 0 成功;其他 失败
  5. */
  6. int (*release) (struct inode *, struct file *);
  1. /*
  2. * @description : 向设备写数据
  3. * @param - : 设备文件,表示打开的文件描述符
  4. * @param - : 要写给设备写入的数据
  5. * @param - : 要写入的数据长度
  6. * @param - : 相对于文件首地址的偏移
  7. * @return : 写入的字节数,如果为负值,表示写入失败
  8. */
  9. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  1. /*
  2. * @description : 从设备读取数据
  3. * @param - : 要打开的设备文件(文件描述符)
  4. * @param - : 返回给用户空间的数据缓冲区
  5. * @param - : 要读取的数据长度
  6. * @param - : 相对于文件首地址的偏移
  7. * @return : 读取的字节数,如果为负值,表示读取失败
  8. */
  9. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  1. unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
  2. unsigned long copy_to_user(void *to, const void __user *from, unsigned long n)

以下是对参数进行讲解:

  • struct inode
  1. /*
  2. * 内核使用inode结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体(file
  3. * 文件结构)是不同的,我们可以使用多个file 文件结构表示同一个文件的多个文件描述符,但此时,所有这
  4. * 些file文件结构全部都必须只能指向一个inode结构体
  5. */
  6. struct inode {
  7. umode_t i_mode;
  8. unsigned short i_opflags;
  9. kuid_t i_uid; /* inode拥有者id */
  10. kgid_t i_gid; /* inode所属群组id */
  11. unsigned int i_flags;
  12. #ifdef CONFIG_FS_POSIX_ACL
  13. struct posix_acl *i_acl;
  14. struct posix_acl *i_default_acl;
  15. #endif
  16. const struct inode_operations *i_op;
  17. struct super_block *i_sb;
  18. struct address_space *i_mapping;
  19. #ifdef CONFIG_SECURITY
  20. void *i_security;
  21. #endif
  22. /* Stat data, not accessed from path walking */
  23. unsigned long i_ino;
  24. /*
  25. * Filesystems may only read i_nlink directly. They shall use the
  26. * following functions for modification:
  27. *
  28. * (set|clear|inc|drop)_nlink
  29. * inode_(inc|dec)_link_count
  30. */
  31. union {
  32. const unsigned int i_nlink;
  33. unsigned int __i_nlink;
  34. };
  35. dev_t i_rdev; /* 若是设备文件,表示记录设备的设备号 */
  36. loff_t i_size; /* inode所代表大少 */
  37. struct timespec i_atime; /* inode最近一次的存取时间 */
  38. struct timespec i_mtime; /* inode最近一次修改时间 */
  39. struct timespec i_ctime; /* inode的生成时间 */
  40. spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
  41. unsigned short i_bytes;
  42. unsigned int i_blkbits;
  43. blkcnt_t i_blocks;
  44. #ifdef __NEED_I_SIZE_ORDERED
  45. seqcount_t i_size_seqcount;
  46. #endif
  47. /* Misc */
  48. unsigned long i_state;
  49. struct mutex i_mutex;
  50. unsigned long dirtied_when; /* jiffies of first dirtying */
  51. unsigned long dirtied_time_when;
  52. struct hlist_node i_hash;
  53. struct list_head i_wb_list; /* backing dev IO list */
  54. struct list_head i_lru; /* inode LRU list */
  55. struct list_head i_sb_list;
  56. union {
  57. struct hlist_head i_dentry;
  58. struct rcu_head i_rcu;
  59. };
  60. u64 i_version;
  61. atomic_t i_count;
  62. atomic_t i_dio_count;
  63. atomic_t i_writecount;
  64. #ifdef CONFIG_IMA
  65. atomic_t i_readcount; /* struct files open RO */
  66. #endif
  67. const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
  68. struct file_lock_context *i_flctx;
  69. struct address_space i_data;
  70. struct list_head i_devices;
  71. union {
  72. struct pipe_inode_info *i_pipe;
  73. struct block_device *i_bdev;
  74. struct cdev *i_cdev; /* 若是字符设备,对应的为cdev结构 */
  75. };
  76. __u32 i_generation;
  77. #ifdef CONFIG_FSNOTIFY
  78. __u32 i_fsnotify_mask; /* all events this inode cares about */
  79. struct hlist_head i_fsnotify_marks;
  80. #endif
  81. void *i_private; /* fs or device private pointer */
  82. };
  83. /* 内核函数从inode中提取设备号 */
  84. /* 提取主设备号 */
  85. static inline unsigned imajor(const struct inode *inode){  return MAJOR(inode->i_rdev);
  86. }
  87. /* 提取次设备号 */
  88. static inline unsigned iminor(const struct inode *inode)
  89. {
  90.   return MINOR(inode->i_rdev);
  91. }
  • struct file
  1. /*
  2. * file结构体指示一个已经打开的文件(设备对应于设备文件),其实系统中的每个打开的文件在内核空间都有
  3. * 一个相应的struct file结构体,它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数,直至文
  4. * 件被关闭。如果文件被关闭,内核就会释放相应的数据结构
  5. */
  6. struct file {
  7. union {
  8. struct llist_node fu_llist;
  9. struct rcu_head fu_rcuhead;
  10. } f_u;
  11. struct path f_path;
  12. struct inode *f_inode; /* cached value */
  13. const struct file_operations *f_op; /* 与文件相关的各种操作 */
  14. /*
  15. * Protects f_ep_links, f_flags.
  16. * Must not be taken from IRQ context.
  17. */
  18. spinlock_t f_lock;
  19. atomic_long_t f_count;
  20. unsigned int f_flags; /* 文件标志,如 O_RDONLY , O_NONBLOCK 以及 O_SYNC */
  21. fmode_t f_mode; /* 此文件模式通过 FMODE_READ , FMODE_WRITE 识别了文件为可读的,可写的,或者是二者 */
  22. struct mutex f_pos_lock;
  23. loff_t f_pos; /* 当前读写文件的位置 */
  24. struct fown_struct f_owner;
  25. const struct cred *f_cred;
  26. struct file_ra_state f_ra;
  27. u64 f_version;
  28. #ifdef CONFIG_SECURITY
  29. void *f_security;
  30. #endif
  31. /* needed for tty driver, and maybe others */
  32. void *private_data; /* 在驱动调用open方法之前,open系统调用设置此指针为NULL值。你可以很自由的将其做为你自己需要的一些数据域或者不管它 */
  33. #ifdef CONFIG_EPOLL
  34. /* Used by fs/eventpoll.c to link all the hooks to this file */
  35. struct list_head f_ep_links;
  36. struct list_head f_tfile_llink;
  37. #endif /* #ifdef CONFIG_EPOLL */
  38. struct address_space *f_mapping;
  39. } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

步骤3: 编写字符设备驱动模块加载 

步骤3.1:创建并注册设备号

  • register_chrdev
  1. /**
  2. * register_chrdev() - create and register a cdev occupying a range of minors
  3. * @major: major device number or 0 for dynamic allocation
  4. * @name: name of this range of devices
  5. * @fops: file operations associated with this devices
  6. *
  7. * If @major == 0 this functions will dynamically allocate a major and return
  8. * its number.
  9. *
  10. * If @major > 0 this function will attempt to reserve a device with the given
  11. * major number and will return zero on success.
  12. *
  13. * Returns a -ve errno on failure.
  14. *
  15. * The name of this device has nothing to do with the name of the device in
  16. * /dev. It only helps to keep track of the different owners of devices. If
  17. * your module name has only one type of devices it's ok to use e.g. the name
  18. * of the module here.
  19. */
  20. static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
  21. /**
  22. * unregister_chrdev - unregister and destroy a cdev
  23. * @major: major device number
  24. * @name: name of this range of devices
  25. *
  26. * Unregister and destroy the cdev occupying the region described by
  27. * @major, @baseminor and @count. This function undoes what
  28. * register_chrdev() did.
  29. */
  30. static inline void unregister_chrdev(unsigned int major, const char *name)
  • register_chrdev_region/alloc_chrdev_region + cdev
  1. /**
  2. * register_chrdev_region() - register a range of device numbers
  3. * @from: the first in the desired range of device numbers; must include
  4. * the major number.
  5. * @count: the number of consecutive device numbers required
  6. * @name: the name of the device or driver.
  7. *
  8. * Return value is zero on success, a negative error code on failure.
  9. */
  10. int register_chrdev_region(dev_t from, unsigned count, const char *name)
  11. /**
  12. * alloc_chrdev_region() - register a range of char device numbers
  13. * @dev: output parameter for first assigned number
  14. * @baseminor: first of the requested range of minor numbers
  15. * @count: the number of minor numbers required
  16. * @name: the name of the associated device or driver
  17. *
  18. * Allocates a range of char device numbers. The major number will be
  19. * chosen dynamically, and returned (along with the first minor number)
  20. * in @dev. Returns zero or a negative error code.
  21. */
  22. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
  23. /**
  24. * unregister_chrdev_region() - return a range of device numbers
  25. * @from: the first in the range of numbers to unregister
  26. * @count: the number of device numbers to unregister
  27. *
  28. * This function will unregister a range of @count device numbers,
  29. * starting with @from. The caller should normally be the one who
  30. * allocated those numbers in the first place...
  31. */
  32. void unregister_chrdev_region(dev_t from, unsigned count)
  33. /**
  34. * cdev_alloc() - allocate a cdev structure
  35. *
  36. * Allocates and returns a cdev structure, or NULL on failure.
  37. */
  38. struct cdev *cdev_alloc(void)
  39. /**
  40. * cdev_init() - initialize a cdev structure
  41. * @cdev: the structure to initialize
  42. * @fops: the file_operations for this device
  43. *
  44. * Initializes @cdev, remembering @fops, making it ready to add to the
  45. * system with cdev_add().
  46. */
  47. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  48. /**
  49. * cdev_add() - add a char device to the system
  50. * @p: the cdev structure for the device
  51. * @dev: the first device number for which this device is responsible
  52. * @count: the number of consecutive minor numbers corresponding to this
  53. * device
  54. *
  55. * cdev_add() adds the device represented by @p to the system, making it
  56. * live immediately. A negative error code is returned on failure.
  57. */
  58. int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  59. /**
  60. * cdev_del() - remove a cdev from the system
  61. * @p: the cdev structure to be removed
  62. *
  63. * cdev_del() removes @p from the system, possibly freeing the structure
  64. * itself.
  65. */
  66. void cdev_del(struct cdev *p)

步骤3.2:创建一个class类型的对象

  1. /* This is a #define to keep the compiler from merging different
  2. * instances of the __key variable */
  3. #define class_create(owner, name) \
  4. ({ \
  5. static struct lock_class_key __key; \
  6. __class_create(owner, name, &__key); \
  7. })
  8. /**
  9. * class_create - create a struct class structure
  10. * @owner: pointer to the module that is to "own" this struct class
  11. * @name: pointer to a string for the name of this class.
  12. * @key: the lock_class_key for this class; used by mutex lock debugging
  13. *
  14. * This is used to create a struct class pointer that can then be used
  15. * in calls to device_create().
  16. *
  17. * Returns &struct class pointer on success, or ERR_PTR() on error.
  18. *
  19. * Note, the pointer created here is to be destroyed when finished by
  20. * making a call to class_destroy().
  21. */
  22. struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
  23. /**
  24. * class_destroy - destroys a struct class structure
  25. * @cls: pointer to the struct class that is to be destroyed
  26. *
  27. * Note, the pointer to be destroyed must have been created with a call
  28. * to class_create().
  29. */
  30. void class_destroy(struct class *cls)

步骤3.3:生成设备节点并导出到用户空间

  1. /**
  2. * device_create- 创建设备
  3. * @class: 该设备依附的类
  4. * @parent: 父设备
  5. * @devt: 设备号(此处的设备号为主次设备号)
  6. * @drvdata:私有数据
  7. * @fmt:设备名
  8. * return:创建的设备指针
  9. */
  10. struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
  11. /**
  12. * device_create- 销毁设备
  13. * @class: 该设备依附的类
  14. * @devt: 设备号(此处的设备号为主次设备号)
  15. */
  16. void device_destroy(struct class *class, dev_t devt)

步骤4: 编写字符设备驱动模块卸载

          查看步骤3

步骤5:指定驱动出入口

  1. /**
  2. * module_init() - driver initialization entry point
  3. * @x: function to be run at kernel boot time or module insertion
  4. *
  5. * module_init() will either be called during do_initcalls() (if
  6. * builtin) or at module insertion time (if a module). There can only
  7. * be one per module.
  8. */
  9. #define module_init(x) __initcall(x);
  10. /**
  11. * module_exit() - driver exit entry point
  12. * @x: function to be run when driver is removed
  13. *
  14. * module_exit() will wrap the driver clean-up code
  15. * with cleanup_module() when used with rmmod when
  16. * the driver is a module. If the driver is statically
  17. * compiled into the kernel, module_exit() has no effect.
  18. * There can only be one per module.
  19. */
  20. #define module_exit(x) __exitcall(x);

步骤6:完善驱动信息

  1. /*
  2. * The following license idents are currently accepted as indicating free
  3. * software modules
  4. *
  5. * "GPL" [GNU Public License v2 or later]
  6. * "GPL v2" [GNU Public License v2]
  7. * "GPL and additional rights" [GNU Public License v2 rights and more]
  8. * "Dual BSD/GPL" [GNU Public License v2
  9. * or BSD license choice]
  10. * "Dual MIT/GPL" [GNU Public License v2
  11. * or MIT license choice]
  12. * "Dual MPL/GPL" [GNU Public License v2
  13. * or Mozilla license choice]
  14. *
  15. * The following other idents are available
  16. *
  17. * "Proprietary" [Non free products]
  18. *
  19. * There are dual licensed components, but when running with Linux it is the
  20. * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
  21. * is a GPL combined work.
  22. *
  23. * This exists for several reasons
  24. * 1. So modinfo can show license info for users wanting to vet their setup
  25. * is free
  26. * 2. So the community can ignore bug reports including proprietary modules
  27. * 3. So vendors can do likewise based on their own policies
  28. */
  29. #define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
  30. /*
  31. * Author(s), use "Name <email>" or just "Name", for multiple
  32. * authors use multiple MODULE_AUTHOR() statements/lines.
  33. */
  34. #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
  35. /* What your module does. */
  36. #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)

# 补充知识

1. 加载和卸载驱动模块

  • 加载模块

        驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块: insmodmodprobe,其中insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。但是 modprobe 就不会存在这个问题, modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe 命令相比 insmod 要智能一些。 modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能,modprobe 命令默认会去/lib/modules/<kernel-version>目录中查找模块,比如本书使用的 Linux kernel 的版本号为 4.1.15,因此 modprobe 命令默认会到/lib/modules/4.1.15 这个目录中查找相应的驱动模块,一般自己制作的根文件系统中是不会有这个目录的,所以需要自己手动创建

insmod drv.ko
modprobe drv.ko

  • 卸载模块

        驱动模块的卸载使用命令“rmmod”即可,也可以使用“modprobe -r”命令卸载驱动

rmmod drv.ko
modprobe -r drv.ko 

  • 查看模块

        输入“lsmod”命令即可查看当前系统中存在的模块 

2. 查看驱动设备

  • 使用 cat /proc/devices 可以查看当前已经被使用掉的设备号

  • 使用  ls /sys/class/ 查看类

  • 使用 ls /sys/class/test_class/ -l 查看设备

3. 查看日志信息 

  •  printk

        printk 运行在内核态。在内核中想要向控制台输出或显示一些内容,必须使用printk 这个函数。不同之处在于, printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件 include/linux/kern_levels.h 里面,定义如下:

  1. #define KERN_SOH "\001"
  2. #define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
  3. #define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
  4. #define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
  5. #define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用
  6. KERN_ERR 报告硬件错误 */
  7. #define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
  8. #define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
  9. #define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
  10. #define KERN_DEBUG KERN_SOH "7" /* 调试信息 */

一共定义了 8 个级别,其中 0 的优先级最高, 7 的优先级最低。如果要设置消息级别,参考如下示例:printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");用 printk 的 时 候 不 显 式 的 设 置 消 息 级 别 , 那 么 printk 将 会 采 用 默 认 级 别MESSAGE_LOGLEVEL_DEFAULT, MESSAGE_LOGLEVEL_DEFAULT 默认为 4

  • printk等级

使用 # cat /proc/sys/kernel/printk  指令查看printk等级

        四个数字分表代表:DEFAULT_CONSOLE_LOGLEVEL(控制台输出等级)、DEFAULT_MESSAGE_LOGLEVEL(默认消息等级)、MINIMUM_CONSOLE_LOGLEVEL(最低输出等级)、DEFAULT_CONSOLE_LOGLEVEL(默认控制台输出等级) 

        当 printk() 中的消息日志级别小于当前控制台日志级别(CONSOLE_LOGLEVEL_DEFAULT)时,printk 的信息就会在控制台上显示,如果我们用 printk 的 时 候 不 显 式 的 设 置 消 息 级 别,即printk使用MESSAGE_LOGLEVEL_DEFAULT(级别为4),此时当前控制台日志级别为CONSOLE_LOGLEVEL_DEFAULT(级别为7),所示直接使用 printk 输出的信息是可以显示在控制台上的。

  • 查看日志

        第一种是使用dmesg命令打印。第二种是通过cat /proc/kmsg来打印

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/330343
推荐阅读
相关标签
  

闽ICP备14008679号