字符驱动编程模型:

1. 设备描述结构cdev

    1.1  结构定义

    1.2  设备号

    1.3  设备操作集

在Linux系统中,设备的类型非常繁多,如:字符设备,块设备,网络接口设备,USB设备,PCI设备,平台设备,混杂设备……,而设备类型不同,也意味着其对应的驱动程序模型不同,这样就导致了我们需要去掌握众多的驱动程序模型。那么能不能从这些众多的驱动模型中提炼出一些具有共性的规则,则是我们能不能学好Linux驱动的关键。



查看设备号可以通过查看/dev目录下设备号看到


次设备号


设备号的操作:


设备号的分配:


设备号的注销:

不论使用何种方法分配设备号,都应该在驱动退出时,使用unregister_chrdev_region函数释放这些设备号。

操作函数集:


操作函数集:



2. 字符设备驱动模型


描述结构的分配:

cdev变量的定义可以采用静态和动态两种方法

静态分配: struct cdev  mdev;

动态分配: struct cdev *pdev = cdev_alloc();

描述结构的初始化:


描述结构的注册:

字符设备的注册使用cdev_add函数来完成,函数原型:

cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数:

p: 待添加到内核的字符设备结构

dev: 设备号

count: 该类设备的设备个数

剩下的就是根据相应的芯片手册,完成初始化。

实现设备操作:


设备操作原型:

打开设备,响应open系统调用

int  (*open)(struct inode *, struct file *)


关闭设备,响应close系统调用

int  (*release)(struct inode *, struct file *)


重定位读写指针,响应lseek系统调用

loff_t (*llseek)(struct file*, loff_t, int)


从设备读取数据,响应read系统调用

ssize_t(*read)(struct file*, char __user *, size_t, lofft *)


向设备写入 数据,响应write系统调用

ssize_t(*write)(struct file*, char __user *, size_t, lofft *)

Struct file: 在linux系统中,每一个打开的文件,在内核中都会关联一个struct file,它由在内核打开时创建,在文件关闭后释放。

重要成员:

loff_t  f_pos /* 文件读写指针 */

struct file_operations *f_op /* 读文件所对应的操作 */


struct inode

每一个存在于文件系统里面的文件都会关联一个inode结构,该结构主要用来记录文件物理上的信息。因此,它和代表打开文件的file结构是不同的。一个文件没有被打开时不会关联file结构,但是会关联一个inode结构。

重要成员: dev_t i_rdev /* 设备号 */


设备操作---open

open设备方法是驱动程序用来为以后的操作完成初始化准备工作的,在大部分驱动程序中,open完成如下工作:

    1> 标明次设备号

    2.> 启动设备

设备操作---release

release方法的作用正好与open相反。这个设备方法有时也称为close, 它的作用是关闭设备

设备操作---read




设备操作---write


最后就是驱动的注销了:当我们从内核中卸载驱动程序的时候,需要使cdev_del函数来完成字符设备的注销。




3. 范例驱动分析


这里给一个编写好的字符驱动示例代码:

memdev.c文件

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

int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);
    
    if (num==0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //无效的次设备号
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/

  /*判断读位置是否有效*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;

  /*读数据到用户空间*/
  if (copy_to_user(buf, register_addr+p, count))
  {
    ret = -EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
  
  /*分析和获取有效的写长度*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;
    
  /*从用户空间写入数据*/
  if (copy_from_user(register_addr + p, buf, count))
    ret = -EFAULT;
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
      case SEEK_SET: 
        newpos = offset;
        break;

      case SEEK_CUR: 
        newpos = filp->f_pos + offset;
        break;

      case SEEK_END: 
        newpos = 5*sizeof(int)-1 + offset;
        break;

      default: 
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>5*sizeof(int)))
    	return -EINVAL;
    	
    filp->f_pos = newpos;
    return newpos;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  
  /* 注册字符设备 */
  alloc_chrdev_region(&devno, 0, 2, "memdev");
  cdev_add(&cdev, devno, 2);
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  unregister_chrdev_region(devno, 2); /*释放设备号*/
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);

Makefile 文件

obj-m := memdev.o
KDIR := /home/S5-driver/lesson7/linux-tiny6410/
all:
	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order

编译驱动模块,通过NFS挂载开发板的根文件系统同步到开发板上



安装驱动模块 insmod memdev.ko


创建字符设备文件:(因为应用程序是通过操作设备文件来和相应的设备驱动通讯的,上面的说明有讲到)

字符设备文件和字符设备驱动又是通过主设备号联系起来的!

cat /proc/device 可以看到主设备号和设备驱动的名字!


从上面可以看到设备驱动memdev对应的设备驱动号为252

下面来通过mknod创建字符设备文件


mdmdev0 为给设备文件取的名字(这里只要不和其他的名字重复就行)

c 代表创建的是字符设备文件

252 是上面设备驱动所对应的主设备号 0代表的是次设备号 ,这里为非负就行

应用程序来通过字符设备文件来访问字符设备驱动程序:(这里操作的是虚拟的字符设备文件,通过上面的memdev.c可以看出)

read-memdev.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd = 0;
	int dst = 0;
	
	/*打开设备文件*/
	fd = open("/dev/memdev0",O_RDWR);
	
	/*写入数据*/
	read(fd, &dst, sizeof(int));
	
	printf("dst is %d\n",dst);
	
	/*关闭设备*/
	close(fd);
	
	return 0;	

}
write-memdev.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd = 0;
	int src = 2013;
	
	/*打开设备文件*/
	fd = open("/dev/memdev0",O_RDWR);
	
	/*写入数据*/
	write(fd, &src, sizeof(int));
	
	/*关闭设备*/
	close(fd);
	
	return 0;	

}


交叉编译,这里使用静态编译!因为一些库开发板上还没有移植过去!

比如上面不使用-static选项编译write-mem.c 在开发板上运行应用程序就会出现



这里可以看到这个应用程序的运行需要有libc.so.6这个动态链接库!然后去开发板的系统上lib目录下去查看有没有这个库。


空的,什么也没有,所以解决这个问题的有两个办法,一个是把这个库复制到lib目录下,一种方法是在编译的时候加上-static选项 选择静态编译的方法编译程序!


至此整个字符设备驱动和应用程序是如何联系在一起的以及整个字符驱动设备模型简介over!

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐