2---linux字符设备进阶篇

概要:上一篇我们编写了一个简单的驱动程序,似乎有点索然无味,其实我也是这样觉得的,所以这篇我们将加大力度。

看了上一篇的字符设备似乎觉得很简单,事实不然。

如何注册字符设备
先来看几个函数:
int register_chrdev_region(dev_t dev_id, unsigned count, const char *name)

静态注册字符设备,自己指定主设备号。什么叫静态,就是你自己静静地动手注册的这个状态,就叫静态,自己动手注册,累人。
dev_id:指定了设备号的起始地址,我们用MKDEV(major,minor)来指定,起始主设备号是major,起始次设备号为minor
count: 你想一次性注册次设备号的个数
*name:名字随意 ,比如”sun_xiao_chuan“
注意:判断返回值,小于0就说明注册失败了。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

动态注册字符设备,内核帮你指定主设备。什么叫动态,就是内核动起来帮你注册的这个状态,就叫动态,我还是很喜欢别人自己动的,咳咳,有点跑题
*dev:内核帮你分配后总得告诉你,它的主次设备号吧,它分配完之后就放进dev里面
baseminor:次设备号的起始号
count:你想一次性注册次设备号的个数
name:名字随意,比如"dai_dai_da_shi_xiong"
注意:判断返回值,小于0就说明注册失败了。

对于这个两个函数看实际情况使用,又或者说你喜欢自己动还是别人动,比如我喜欢别人动,那就这么注册字符设备:

 alloc_chrdev_region(&devid, 0, 3, "hello");
 //那么我就让内核帮我分配字符设备,分配完之后总得告诉我主设备吧?
 major = MAJOR(devid)
 //使用这个宏定义来获取主设备

有始有终,你在入口函数注册了我,我就在出口函数注销你

unregister_chrdev_region(MKDEV(major, 0), 3);

在出口函数,使用这个函数可以把它注销掉

字符设备的精髓:file_operations:

file_operations到底是这什么东西?它赋予了字符设备生命,又或者说它这是字符设备的技能。你创建的字符设备可以有什么技能,这个完全由你来决定,是不是觉得自己就像上帝一样。
比如说我想我的字符设备能进行写操作,那就要编写属于这个字符设备的写操作函数, 比如说我想让我的字符设备能帮我找小电影,那就编写一个…好吧,这里面没有能找小电影的函数。

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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	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 *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	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 (*dir_notify)(struct file *filp, unsigned long arg);
	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);
};

根据自己需求来写file_operafile,比如:

static struct const file_operation hello_fops = {
	.owner = THIS_MODULE, //这个fops只属于我这个字符设备
	.open = hello_open, //每当有人打开了我这个字符设备就会执行 hello_open,或者说你来打开我的字符设备我就会找小电影
};

那么完成了自己的fops,怎么告诉内核这个fops是我字符设备的fops:

我们需要使用一个结构体cdev:

struct cdev {
       struct kobject    kobj;                   // 内嵌的kobject对象 
       struct module   *owner;                   //所属模块
       const struct file_operations  *ops;     //操作方法结构体
       struct list_head  list;            //与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
       dev_t dev;                    //起始设备编号,可以通过MAJOR(),MINOR()来提取主次设备号
       unsigned int count;                //连续注册的次设备号个数
}

使用cdev之前进行初始化:

static struct cdev hello_dev;
void cdev_init(&hello_dev, &hello_fops);

cdev:这里装了这个字符设备基本信息。内核就像xx局一样,你要去xx局弄一张身份证,你交一大堆材料,xx局确认你是个人之后,给你一张身份证,从此中国又多了一个人。这个身份证就是cdev。
file_operations:这里就是我们刚刚创建的那个能找小电影的fops

回想一下,我们之前注册了字符设备,但好像内核并没有把我的字符设备放进内核吧
cdev_add(&hello_cdev, devid,2);

这一步就是把我的cdev告诉了内核,或者说就是把我这个字符设备的主次设备、fops,告诉了内核。

void cdev_del(&hello_cdev);

在出口函数,使用这个函数删除这个结构体

到这一步起始我们的驱动程序就能用起来了:

我这篇文章的例子用到的是动态注册字符设备,主设备号我们自己要动手拿出来。然后我还是要谈一下fops的归属问题,我自己写的这个fops有谁能用,假设我们动态注册的major(主设备号)为255,minor(次设备)是从1开始的,次设备号的个数为3,那么major为255,minor从1到3的这些字符设备都拥有我写的fops

我们还是老样子
1.编写makefile
2.使用make编译
3.把ko文件拷贝到开发板上
4.insmod hello.ko

我们的驱动就被加载进内核了,我们来编写一个测试程序

****hello_test.c****

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{

  int fd;
  fd=open(argv[1],O_RDWR); //我们的fops只有.open函数,所以我们就只打开/dev/hello
  if(fd<0)
      printf("can't open %s \n",argv[1]);
  else
      printf("can open %s \n",argv[1]);
  return 0;
}

5.编译测试程序:arm-linux-gcc
6.拷贝到开发板
7.运行测试程序

 ./hello_test   /dev/hello

这时候百分百是打印:can’t open /dev/hello,我们打开失败了,为什么?
答:因为在/dev目录下根本没有hello这个字符设备节点,所以我们要创建这个文件。

8 创建设备节点之前我们先查看一下内核分配的主设备号是多少
使用命令:cat /proc/device 得到major为255

9.使用mknod命令设备节点

mknod  /dev/hello c  255  1  
mknod  /dev/hello1 c  255 2
mknod  /dev/hello2 c  255 3
//第一个参数是设备名字
//设备的类型
//主设备号
//次设备号
  1. 运行测试程序:
 ./hello_test   /dev/hello1
 ./hello_test   /dev/hello2
 ./hello_test   /dev/hello3

输出:

can open /dev/hello1
can open /dev/hello2
can open /dev/hello3

自动创建设备节点

我们这样自己这样一个一个地创建设备节点真的很累,我说过我喜欢别人来动,所以内核有提供自动创建设备节点的函数。

static struct class *cls;
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");

创建名为“hello”的这个类
这个类下面有 “hello0” ,“hello1” ,“hello2”,这些设备节点

      	class_device_destroy(cls, MKDEV(major, 0));
        class_device_destroy(cls, MKDEV(major, 1));
        class_device_destroy(cls, MKDEV(major, 2));
        class_destroy(cls);

在出口函数,使用这些函数来卸载设备节点和类

重新make ,拷贝到开发板,加载驱动程序:

insmod hello.ko

查看/dev目录下有没有名为“hello*”的设备节点

ls -l /dev/hello*

运行测试程序:

 ./hello_test   /dev/hello1
 ./hello_test   /dev/hello2
 ./hello_test   /dev/hello3

一步成功,完全不需要自己手动创建
到这里字符设备被我加载进内核,并且测试程序能调用到fops里面的hello_open()函数

*****hello.c*****
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/list.h>
#include <linux/cdev.h>

static int major;

static int hello_open(struct inode *inode, struct file *file)
{
        printk("hello_open\n");
        return 0;
}

static struct file_operations hello_fops = {
        .owner = THIS_MODULE,
        .open  = hello_open,  //自己编写的open()函数
};
static struct cdev hello_cdev;
static struct class *cls;

static int hello_init(void)//入口函数
{
        dev_t devid;
        
        major = register_chrdev(0, "hello", &hello_fops);
        if (major) //静态注册
        {
              devid = MKDEV(major, 0);
             register_chrdev_region(devid, 3, "hello");  
        } 
        else  //动态注册
        {
              alloc_chrdev_region(&devid, 0, 3, "hello"); 
              major = MAJOR(devid);            				  
        }

        cdev_init(&hello_cdev, &hello_fops); //初始化cdev结构体
        cdev_add(&hello_cdev, devid, HELLO_CNT); //向内核添加cdev结构体


        cls = class_create(THIS_MODULE, "hello");  //自动创建设备节点
		class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
        class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
        class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */


        return 0;
        
 }
static void hello_exit(void) //出口函数
{
        class_device_destroy(cls, MKDEV(major, 0));
        class_device_destroy(cls, MKDEV(major, 1));
        class_device_destroy(cls, MKDEV(major, 2));
        class_destroy(cls);

        cdev_del(&hello_cdev);
        unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值