一、复习
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{
...
struct files_struct *files;
...
}
同理,打开文件的时候,系统会创建一个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);
-
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*);
...
}
- **读/写:**对用户层来讲,用户读(从内核空间拷贝数据到用户空间),用户写(从用户空间拷贝数据到内核层)。
- 使用的内核空间和用户空间数据拷贝函数:
int copy_from_user(void *to,const void __user *from,int n);
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;
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));
cdevp = cdev_alloc();
if(cdevp == NULL){
printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
__FILE__,__func__,__LINE__);
ret = -ENOMEM;
goto err1;
}
cdev_init(cdevp,&fops);
ret = cdev_add(cdevp,dev_no,COUNT);
if(ret<0){
goto err1;
}
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;
}
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_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
加载执行(根据编译的架构,放在对应的平台上执行)
自己写应用层代码:调用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)))
#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;
}
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;
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));
cdevp = cdev_alloc();
if(cdevp==NULL){
printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
__FILE__,__func__,__LINE__);
ret=-ENOMEM;
goto err1;
}
cdev_init(cdevp,&fops);
ret = cdev_add(cdevp,dev_no,COUNT);
if(ret<0){
goto err1;
}
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;
}
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_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打印内核信息,进行对比
四、访问硬件 操作系统运行起来后,访问的是虚拟地址,我们在数据手册中读到的是设备的物理地址,所以我们需要通过物理地址,获取到对应的虚拟地址。
void__iomem*ioremap(phys_addr_t offset,unsigned long size)
void iounmap(void__iomem*addr)
readl(c)
writel(v,c)
|