第七讲 Linux字符设备驱动2

一、复习

1.1、字符设备驱动编写

  • alloc_chrdev_region/register_chrdev_region
  • cdev_alloc
  • cdev_init
  • cdev_add
  • class_create
  • device_create
    注意:错误处理,goto语句
    卸载的时候:释放申请的资源,并删除注册的结构体

1.2、设备文件创建

  • mknod
  • 通过udev/mdev创建设备文件(根据uevent文件中的设备文件信息创建)
  • 一个驱动对应驱动多个同类型设备:cdev_demo0、cdev_demo1、cdev_demo2,而应用层打开设备文件,在驱动中,怎么区别应用层打开的是哪一个?
    • 通过次设备号进行区分—>>>怎么去读出次设备号?
      • 应用层open(设备文件名)—系统调用—>>> fops -> open(struct inode *,struct file *)
      • inode结构体中的设备号成员变量区分

二、open的系统调用过程

2.1、应用层

设备文件是创建的:根据设备文件名,设备号,系统在创建文件的时候会创建一个结构体,描述被创建的文件的所有信息:inode 结构体

struct inode{
	dev_t i_rdev;
	union{
			struct pipe_inode_info *i_pipe;
        	struct block_device	*i_bdev;
			struct cdev*i_cdev;
	};
}

在一个进程中调用open(),通过系统调用接口,进入内核层。task_struct结构体,描述的是进程所有的信息。

struct task_struct{
	...
	/* files information which opened by task */
	struct files_struct *files;	//struct file *fd_array[NR_OPEN_DEFAULT];
	...
}

同理,打开文件的时候,系统会创建一个file结构体描述被打开的文件的所有信息,这个结构体指针被存在当前进程维护的一个fd_array[NR_OPEN_DEFAULT]数组中,存的位置就是打开文件的fd值。系统为当前进程task默认打开的标准输入、标准输出和标准错误输出文件占用了数组的前3项0、1、2:

strcut file{
	...
	unsigned int f_flags;
	const struct file_operations *f_op;
	...
}

2.2、open系统调用

  • 打开/fs/open.c文件:
  • SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
    • do_sys_open(AT_FDCWD, filename, flags, mode);
      • struct file *f = do_filp_open(dfd, tmp, &op, lookup);
        • filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
          • do_last(nd, &path, op, pathname);
            • filp = nameidata_to_filp(nd);
              • filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp, NULL, cred);
                • f->f_op = fops_get(inode->i_fop);
  1. if(!open && f->f_op)
    	open = f->f_op->open;
    if(open){
    	error = open(inode,f);
    }
    

    三、文件接口——操作方法集

    3.1、read/write

    struct file_operations{
    	ssize_t (*read)(struct file*,char __user*,size_t,loff_t*);
    	ssize_t (*write)(struct file*,constchar __user*,size_t,loff_t*);
    	...
    }
    
    • **读/写:**对用户层来讲,用户读(从内核空间拷贝数据到用户空间),用户写(从用户空间拷贝数据到内核层)。
    • 使用的内核空间和用户空间数据拷贝函数:
    /*
    *@to:	内核空间缓存区地址
    *@from:用户空间缓存器地址
    *@n:拷贝的字节数
    */
    //从用户层拷贝数据,在驱动中的write接口中调用
    int copy_from_user(void *to,const void __user *from,int n);
    
    /*
    *@to:	用户空间缓存区地址
    *@from:内核空间缓存器地址
    *@n:拷贝的字节数
    */
    //拷贝给用户,在驱动中的read接口中调用
    int copy_to_user(void __user *to,const void *from,int n);
    

    3.2、读写例程

    #include <linux/init.h>
    #include<linux/module.h>
    #include<linux/cdev.h>
    #include<linux/fs.h>
    #include<linux/device.h>
    #include<linux/uaccess.h>
    
    #defineNAME"cdev_demo"
    #defineCOUNT3
    #defineKBUFSIZE64
    
    dev_t dev_no;
    struct cdev*cdevp=NULL;
    structclass*cls=NULL;
    struct device*devp=NULL;
    
    char Kbuf[KBUFSIZE]={'\0'};
    int Kbufcount=0;
    
    static int demo_open(struct inode *inode,struct file *filp)
    {
    	printk(KERN_DEBUG"[%s-%s-%d]:runned...\n",__FILE__,__func__,__LINE__);
    	return 0;
    }
    
    static int demo_release(struct inode *inode, struct file *filp)
    {
    	printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
               __FILE__,__func__,__LINE__);
    	return 0;
    }
    
    ssize_t demo_read(struct file *filp, char __user *buf, size_t size,loff_t *pos)
    {
    	if(size > Kbufcount){
            size = Kbufcount;
         }
    	if(copy_to_user(buf,Kbuf,size)){
    		printk(KERN_ERR"[%s-%s-%d]: copy_to_user failed...\n",\
               __FILE__,__func__,__LINE__);
    		return-EAGAIN;
    	}
    	Kbufcount=0;
    	printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
               __FILE__,__func__,__LINE__);
    	return size;
    }
    
    ssize_t demo_write(struct file *filp, const char __user *buf, size_t size,loff_t *pos)
    {
    	if(size > KBUFSIZE){
            size = KBUFSIZE;
    	}
    	if(copy_from_user(Kbuf,buf,size)){
            printk(KERN_ERR"[%s-%s-%d]: copy_from_user failed...\n",\
                   __FILE__,__func__,__LINE__);
    		return -EAGAIN;
    	}
    	Kbufcount = size;
        printk(KERN_DEBUG"[%s-%s-%d]: Kbuf:%s...\n",\
               __FILE__,__func__,__LINE__,Kbuf);
    	return size;
    }
    
    struct file_operations fops={
    	.owner = THIS_MODULE,
    	.open = demo_open,
    	.release = demo_release,
    	.read = demo_read,
    	.write = demo_write,
    };
    
    static int __initdemo_init(void)
    {
    	int ret=0,i=0;
    	//0、申请设备号
    	ret = alloc_chrdev_region(&dev_no,0,COUNT,NAME);
    	if(ret<0){
    		printk(KERN_ERR"[%s-%s-%d]:alloc_chrdev_region failed...\n",\
    			__FILE__,__func__,__LINE__);
    	goto err0;
    	}
    	printk(KERN_DEBUG"[%s-%s-%d]:devno->major:%d--minor:%d--...\n",\
           __FILE__,__func__,__LINE__,MAJOR(dev_no),MINOR(dev_no));
    	
        //1、分配cdev结构体
    	cdevp = cdev_alloc();
    	if(cdevp == NULL){
            printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
                   __FILE__,__func__,__LINE__);
    		ret = -ENOMEM;
    		goto err1;
    	}
    	
        //2、初始化cdev结构体
    	cdev_init(cdevp,&fops);
    
    	//3、添加到内核中,由内核统一管理
    	ret = cdev_add(cdevp,dev_no,COUNT);
    	if(ret<0){
    		goto err1;
    	}
    
    	//4、class create
    	cls = class_create(THIS_MODULE,NAME);
    	if(IS_ERR(cls)){
    		printk(KERN_ERR"[%s-%s-%d]:class_create...\n",\
               __FILE__,__func__,__LINE__);
    		ret = PTR_ERR(cls);
    		goto err2;
    	}
    
    	//5、device create
    	for(i=0; i < COUNT; i++){
        	devp = device_create(cls,NULL,MKDEV(MAJOR(dev_no),i),\
                    NULL,"%s%d",NAME,i);
            if(IS_ERR(devp)){
    			printk(KERN_ERR"[%s-%s-%d]:device_create[%d]...\n",\
               			__FILE__,__func__,__LINE__,i);
    			ret=PTR_ERR(devp);
    			goto err3;
    		}
    	}
    	return 0;
    err3:
        for(--i;i>=0;i--){
    		device_destroy(cls,MKDEV(MAJOR(devno),i));
        }
    	class_destroy(cls);
    err2:
    	cdev_del(cdevp);
    err1:
    	unregister_chrdev_region(dev_no,COUNT);
    err0:
    	return ret;
    }
    
    static void __exit demo_exit(void)
    {
    	int i=0;
    	for(i=0;i < COUNT;i++){
    		device_destroy(cls,MKDEV(MAJOR(dev_no),i));
    	}
    	class_destroy(cls);
    	//cdev从内核中删除
    	cdev_del(cdevp);
    	//设备号资源释放
    	unregister_chrdev_region(dev_no,COUNT);
    }
    
    module_init(demo_init);
    module_exit(demo_exit);
    MODULE_LICENSE("GPL");
    

    使用外部编译的方法进行编译,生成内核模块:make (KDIR路径:根据自己的路径修改)

    KDIR:=/home/edu/SAMBA_SHARE/BK2101/Driver/03_kernel/kernel-3.4.39
    #KDIR:=/usr/src/linux-headers-4.4.0-203-generic
    
    PWD:=$(shell pwd)
    obj-m+=demo.o
    
    modules:
    	make-C$(KDIR)M=$(PWD) modules
    
    clean:
    	make-C$(KDIR)M=$(PWD) clean
    

    加载执行(根据编译的架构,放在对应的平台上执行)

# insmod demo.ko
# echo"aaaaa" > /dev/cdev_demo0(open ---> write ---> close)
# cat /dev/cdev_demo0(open ---> read ---> close)
# dmesg

自己写应用层代码:调用read/wirte函数

3.2、ioctl接口

NAME
	ioctl-	control device

SYNOPSIS
	#include<sys/ioctl.h>

 int ioctl(int fd,unsigned long request,...);

long (*unlocked_ioctl) (struct file *, unsigned cmd, unsigned args);

3.2.1、内核提供的封装命令宏函数
dir size   type nr
30	16		8	0

#define _IOC(dir,type,nr,size)\
 (((dir) << _IOC_DIRSHIFT)|\
	((type) << _IOC_TYPESHIFT)|\
	((nr) << _IOC_NRSHIFT)|\
	((size) << _IOC_SIZESHIFT))

#define _IO(type,nr)	_IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
/*used to decode ioctl numbers.. */
/*从命令码中提取DIR、TYPE、NR、SIZE */

#define _IOC_DIR(cmd)	(((cmd)>>_IOC_DIRSHIFT)&_IOC_DIRMASK)
#define _IOC_TYPE(cmd)	(((cmd)>>_IOC_TYPESHIFT)&_IOC_TYPEMASK)
#define _IOC_NR(cmd)	(((cmd)>>_IOC_NRSHIFT)&_IOC_NRMASK)
#define_IOC_SIZE(cmd)	(((cmd)>>_IOC_SIZESHIFT)&_IOC_SIZEMASK)
3.2.2、例程

头文件:

#ifndef _IOCTL_DEMO_H
#define _IOCTL_DEMO_H
#include <asm-generic/ioctl.h>

enum demo_speep{
	speed_1 = 0,
	speed_2,
	speed_3,
};

#define CMD_ON _IO('k',0)
#define CMD_OFF _IO('k',1)
#define CMD_SPEED _IOW('k',2,int)
#endif

驱动层源文件:

#include <linux/init.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/uaccess.h>
#include"ioctl_demo.h"

#define NAME "cdev_demo"
#define COUNT 3
#define KBUFSIZE 64

dev_t dev_no;
struct cdev *cdevp = NULL;
struct class *cls = NULL;
struct device *devp = NULL;

static int demo_open(struct inode*inode,struct file*filp)
{
 printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",__FILE__,__func__,__LINE__);
 return0;
}

static int demo_release(struct inode*inode,struct file*filp)
{
 printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
        __FILE__,__func__,__LINE__);
return 0;
}

static long demo_ioctl(struct file*filp,unsigned int cmd,unsigned long args)
{
 switch(cmd){
     case CMD_ON:
         printk(KERN_DEBUG"CMD_ON...\n");
			break;
		case CMD_OFF:
         printk(KERN_DEBUG"CMD_OFF...\n");
			break;
		case CMD_SPEED:
         printk(KERN_DEBUG"CMD_SPEED...\n");
			printk(KERN_DEBUG"speed:%lu\n",args);
			break;
		default:
			break;
	}
 
/*
  printk(KERN_DEBUG"[%s-%s-%d]: ---cmd:%u---args:%lu----\n",\
			__FILE__,__func__,__LINE__,cmd,args);
 */

return 0;
}

struct file_operations fops={
 .owner = THIS_MODULE,
	.open  = demo_open,
	.release = demo_release,
	.unlocked_ioctl=demo_ioctl,
};

static int __initdemo_init(void)
{
 int ret=0,i=0;
	//0、申请设备号
	ret = alloc_chrdev_region(&dev_no,0,COUNT,NAME);
	if(ret<0){
     printk(KERN_ERR"[%s-%s-%d]:alloc_chrdev_region failed...\n",\
            __FILE__,__func__,__LINE__);
goto err0;
 }
 printk(KERN_DEBUG"[%s-%s-%d]:devno->major:%d--minor:%d--...\n",\
        __FILE__,__func__,__LINE__,MAJOR(dev_no),MINOR(dev_no));
//1、分配cdev结构体
cdevp = cdev_alloc();
if(cdevp==NULL){
		printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
            __FILE__,__func__,__LINE__);
		ret=-ENOMEM;
		goto err1;
 }

//2、初始化cdev结构体
cdev_init(cdevp,&fops);
//3、添加到内核中,由内核统一管理
ret = cdev_add(cdevp,dev_no,COUNT);
if(ret<0){
		goto err1;
}

//4、class create
cls=class_create(THIS_MODULE,NAME);
if(IS_ERR(cls)){
		printk(KERN_ERR"[%s-%s-%d]:class_create...\n",\
            __FILE__,__func__,__LINE__);
		ret=PTR_ERR(cls);
		goto err2;
}

//5、device create
for(i=0;i<COUNT;i++){
		devp=device_create(cls,NULL,MKDEV(MAJOR(dev_no),i),NULL,"%s%d",NAME,i);
		if(IS_ERR(devp)){
			printk(KERN_ERR"[%s-%s-%d]:device_create[%d]...\n",\
                __FILE__,__func__,__LINE__,i);
			ret=PTR_ERR(devp);
			goto err3;
		}
}
return 0;
err3:
 for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(MAJOR(dev_no),i));
	}
 class_destroy(cls);
err2:
 cdev_del(cdevp);
err1:
 unregister_chrdev_region(dev_no,COUNT);
err0:
 return ret;
}


static void __exit demo_exit(void)
{
 int i=0;
 for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(MAJOR(dev_no),i));
	}
  class_destroy(cls);

//cdev从内核中删除
cdev_del(cdevp);
//设备号资源释放
unregister_chrdev_region(dev_no,COUNT);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

应用层源文件:

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

#include"ioctl_demo.h"

int main(int argc,constchar*argv[])
{
	int fd=open("/dev/cdev_demo2",O_RDWR);
	if(fd<0){
		perror("open");
	}

	ioctl(fd,CMD_ON);
	ioctl(fd,CMD_OFF);
	ioctl(fd,CMD_SPEED,speed_2);
	close(fd);
	return 0;
}

编译内核模块,和应用层C文件,加载内核模块到操作系统中,执行应用层程序,使用dmesg打印内核信息,进行对比


四、访问硬件

操作系统运行起来后,访问的是虚拟地址,我们在数据手册中读到的是设备的物理地址,所以我们需要通过物理地址,获取到对应的虚拟地址。

/*功能:物理地址映射成虚拟地址
 *@offset 物理地址
 *@size 映射的字节数
 *返回值:虚拟地址
*/

void__iomem*ioremap(phys_addr_t offset,unsigned long size)

/*
 *功能解映射
 *@addr虚拟地址
 */

void iounmap(void__iomem*addr)

readl(c) //读地址中的数据(4byte), @c虚拟地址
writel(v,c) //写数据到制定的地址(4byte),@v写入的值@c虚拟地址
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值