0
点赞
收藏
分享

微信扫一扫

【简单的字符设备驱动实验】奔跑吧Linux内核


文章目录

  • ​​简单字符设备驱动框架代码​​
  • ​​Makefile​​
  • ​​编译加载​​
  • ​​生成设备节点​​
  • ​​编写测试程序​​
  • ​​release() 与 close()​​
  • ​​Linux 内核设备驱动​​
  • ​​设备驱动文件方法集 —— struct file_operations​​
  • ​​misc 机制​​
  • ​​file 结构体​​

简单字符设备驱动框架代码

  1. 申请设备号(alloc_chrdev_region())
  2. 动态分配设备驱动对象(cdev_alloc())
  3. 建立对象与方法的关系(cdev_init())
  4. 设备驱动对象和设备号关系的建立(cdev_add())
  5. 释放:
  1. cdev_del(设备驱动对象) 和 cdev_alloc() 相辅相成
  2. unregister_chrdev_region(设备号)

编写字符设备驱动内核模块 ​​mydemo_dev.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;
static struct cdev* demo_cdev;
/*
struct cdev{
struct kobject kobj; // 用于linux设备驱动模型
struct module *owner; // 字符设备驱动所在的内核模块对象指针
const struct file_operations *ops; // 字符设备驱动中最关键的一个操作函数
struct list_head list; // 用来将字符设备串成一个链表
dev_t dev; // 字符设备的设备号,分为主设备号和次设备号
usigned int count; // 同属于某个主设备号的次设备号的个数

};

*/
static signed count = 1;

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 int demodrv_release(struct inode* inode, struct file* file)
{
return 0;
}

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,
.release = demodrv_release,
.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.");
return ret;
}

demo_cdev = cdev_alloc(); // 动态产生cdev数据结构的方式
if(!demo_cdev)
{
printk("cedv_alloc failed\n");
goto unregister_chrdev;
}
cdev_init(demo_cdev, &demodrv_fops); // 初始化cdev数据结构,建立设备与驱动操作方法集file_operations之间的连接关系
ret = cdev_add(demo_cdev, dev, count); // 把一个字符设备添加到系统中 cdev数据结构,设备的设备号,这个主设备可以有多少个次设备号
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); // 从系统释放cdev数据结构,和cdev_alloc()相辅相成
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_AUTHOR("Jia ming");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple character device");

Makefile

BASEINCLUDE ?= /lib/modules/`uname -r`/build
mydemo-objs := my_demodev.o
obj-m := mydemo.o

all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean
rm -f *ko

编译加载

make
sudo insmod mydemo.ko # 加载内核模块

查看是否加载成功:

...
[225302.379533] successed register char device: my_demo_dev
[225302.379545] Major number = 511, minor number = 0

生成设备节点

生成的设备需要在 /dev/ 目录下生成对应的节点,手动生成,目的在于提供用户空间的读写接口。

sudo mknod /dev/demo_drv c 511 0 # 设备文件名 b/c 主 从, 参考 dmesg 返回的主从设备号
jiaming@ubuntu:~/Documents/CProject/cdrvKModule$ ls -l /dev/
total 0
...
crw-r--r-- 1 root root 511, 0 Aug 31 22:52 demo_dev
...

编写测试程序

在用户空间编写测试程序 ​​test.c​​。

#include <stdio.h>
#include <fcntl.h>

#define DEMO_DEV_NAME "/dev/demo_dev"

int main()
{
char buffer[64];
int fd;

fd = open(DEMO_DEV_NAME, O_RDONLY);
if(fd < 0)
{
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
read(fd, buffer, 64);
close(fd);
return 0;
}
cc test.c -o test
./test

测试结果: ​​dmesg​

...
[226455.119117] demodrv_open: major=511, minor=0
[226455.119137] demodrv_read enter

符合预期。

release() 与 close()

并不是每个 close 系统调用都会引起对 release 方法的调用,只有那些真正释放设备数据结构的close调用才会调用这个方法。内核对每个file结构维护一个引用计数器;无论是 fork 还是 dup,都不会创建数据结构(仅由 open 创建),它们只是增加了已有数据结构中的计数;只有在 file 结构的计数器为 0 时,close 系统调用才会执行 release 方法,这只是删除结构时才发生;release 和 close 的关系保证了对于每次 open 驱动程序,只有一次 release 调用;

Linux 内核设备驱动

【简单的字符设备驱动实验】奔跑吧Linux内核_linux

设备驱动文件方法集 —— struct file_operations

struct file_operations {
struct module *owner;
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 *); // 把用户空间的数据写入设备,并返回成功写入的字节数
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *); // 用来查询设备是否可以立即读写,主要用于阻塞型IO操作
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // 与设备相关的控制命令的实现
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); // 同上
int (*mmap) (struct file *, struct vm_area_struct *); // 用来将设备内存映射到进程的虚拟地址空间中
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *); // 用来打开设备
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); // 用来关闭设备
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

misc 机制

1. 编写驱动代码

// my_miscdemo.c

#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>

#define DEMO_NAME "my_miscdemo_dev"
static struct device *mydemodrv_device;

static signed count = 1;

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 int demodrv_release(struct inode* inode, struct file* file)
{
return 0;
}

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,
.release = demodrv_release,
.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);
if(ret)
{
printk("failed register misc device\n");
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("successed register misc char device: %s\n", DEMO_NAME);
return 0;

}

static void __exit simple_char_exit(void)
{
printk("removing misc device\n");
misc_deregister(&mydemodrv_misc_device);
}

module_init(simple_char_init);
module_exit(simple_char_exit);

MODULE_AUTHOR("Jia ming");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple character misc_device");

2. 编写makefile

BASEINCLUDE ?= /lib/modules/`uname -r`/build
my_miscdemo-objs := misc_demodev.o
obj-m := my_miscdemo.o

all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean
rm -f *ko

3. 编译安装

make
sudo insmod my_miscdemo.ko

4. 已自动创建好设备节点

...
crw------- 1 root root 10, 54 Sep 2 11:02 my_miscdemo_dev
...

使用命令来赋予权限,否则测试程序可能打不开:​​sudo chmod 777 /dev/my_miscdemo_dev​

5. 编写测试程序

#include <stdio.h>
#include <fcntl.h>

#define DEMO_DEV_NAME "/dev/my_miscdemo_dev"

int main()
{
char buffer[64];
int fd;

fd = open(DEMO_DEV_NAME, O_RDONLY);
if(fd < 0)
{
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
read(fd, buffer, 64);
close(fd);
return 0;
}

6. dmesg 查看结果

...
[ 5667.337319] successed register misc char device: my_miscdemo_dev
[ 6222.256272] demodrv_open: major=10, minor=54
[ 6222.256277] demodrv_read enter

file 结构体

struct file {

  union {

   struct list_head fu_list; //文件对象链表指针linux/include/linux/list.h

   struct rcu_head fu_rcuhead; //RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制

   } f_u;

  struct path f_path; //包含dentry和mnt两个成员,用于确定文件路径

  #define f_dentry f_path.dentry //f_path的成员之一,当前文件的dentry结构

  #define f_vfsmnt f_path.mnt //表示当前文件所在文件系统的挂载根目录

  const struct file_operations *f_op; //与该文件相关联的操作函数

  atomic_t f_count; //文件的引用计数(有多少进程打开该文件)

  unsigned int f_flags; //对应于open时指定的flag

  mode_t f_mode; //读写模式:open的mod_t mode参数

loff_t f_pos;//当前文件指针位置

  off_t f_pos; //该文件在当前进程中的文件偏移量

  struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。

  unsigned int f_uid, f_gid;// 文件所有者id,所有者组id

  struct file_ra_state f_ra; //在linux/include/linux/fs.h中定义,文件预读相关

  unsigned long f_version;//记录文件的版本号,每次使用之后递增

  #ifdef CONFIG_SECURITY

   void *f_security;

  #endif

  /* needed for tty driver, and maybe others */

  void *private_data;//使用这个成员来指向分配的数据

  #ifdef CONFIG_EPOLL

  /* Used by fs/eventpoll.c to link all the hooks to this file */

   struct list_head f_ep_links;

   spinlock_t f_ep_lock;

  #endif /* #ifdef CONFIG_EPOLL */

  struct address_space *f_mapping;

  };


举报

相关推荐

0 条评论