linux驱动学习3:实现一简单完整驱动(包括open,read,write,ioctl)

目的:将内核内存的一块作为字符设备,用户可通过这些调用来读写这段内存。

总结:

1、设备号

主设备号标识设备对应的驱动程序,次设备号由内核使用,用于确定设备文件所指的设备。

通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。

设备编号用dev_t表示(linux/types.h  32位,其中12位表示主设备号,20位表示次设备号)。

dev_t获得主设备号或次设备号:MAJOR(dev_t dev); MINOR(dev_t dev)

已知主设备号和次设备号来获取dev_t类型:MKDEV(int  major,  int  minor)

获取一个或多个设备编号int  register_chrdev_region(dev_t first,  unsigned int  count,  char  *name);(静态分配,事先已知道设备号)

动态分配设备编号int  alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 调用成功后dev会保存已分配的第一个编号。

释放设备编号:void unregister_chrdev_region(dev_t first, unsigned int count);

接下来,驱动程序需要将设备编号和内部函数连接起来。

注:(下一步可尝试采用动态分配设备号

 

动态分配设备号缺点:不能预先创建设备节点(因为分配的设备号不能保证始终一致)。

2、文件操作file_operations:

这些操作将与设备编号连接起来。

__user:用于文档,表明该指针是一个用户空间地址。

主要成员:open,  ioctl,  read,  write,  llseek

3struct file结构  linux/fs.h 文件描述符

每打开一个文件,内核就会创建一个对应的file结构,在open()时创建,同时会传递给在该文件上进行操作的所有函数(因为file结构中包含file_operations结构,而该结构包含了所有驱动操作的函数)。

内核中用filp作为执行file结构的指针。

主要成员:

Mode_t  f_mode;  loff_t  f_pos;   struct file_operations *f_pos;  void  private_data; 

4inode结构

对单个文件只有一个inode,而可能有多个file(由于forkdup操作)

主要成员:

dev_t  i_rdev; 对表示设备文件的inode结构,该字段包含真正的设备编号。

struct cdev  *i_cdev;   该结构表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含指向struct cdev结构的指针。

inode中获取设备号: iminor(struct inode *inode);  imajor(inode);

5、字符设备注册  /linux/cdev.h

内核使用struct cdev结构表示字符设备,所以在内核调用该设备操作之前,需要分配并注册一个或者多个该结构。

注册有两种方式:

新方法:

1)定义字节的结构:

struct my_dev {

         struct  cdev  cdev; //此处如果定义的是指针类型,则需要申请分配内存

}my_dev;

//my_dev ->cdev = cdev_alloc();  //如果cdev是指针则需要这一步

my_dev->cdev.ops = &my_fops;

my_dev->cdev.owner = THIS_MODULE;

2)再调用cdev_init(struct cdev *cdev,  struct file_operations *fops);

3)调用cdev_add(struct cdev *dev,  dev_t num,  unsigned int count);

上面每一步都要判断函数调用是否出错。

旧办法(老接口):

注册:int register_chrdev(); 移除:int unregister_chrdev()

 

6、各操作函数实现

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

完成以下工作:传入一个inode,创建一个file结构

n  检查设备特定错误(如未就绪);

n  如果设备首次打开,则进行初始化;

n  必要时更新f_op指针;

n  分配并填写filp->private_data

注:inode结构是传入的参数,对应一个特定的设备(这就是为什么要在/devmknodde 原因),而file结构的filp是要修改的参数(传出),对应该设备的一个文件描述符,也就是一个inode可能有多个file描述符,而每个描述符需要保存inode的信息,即存放在filp->private_data中。

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

完成工作:传入一个inode,释放这个file结构

n  释放由open分配的、保存在filp->private_data中的内容;

n  在最后一次close时关闭设备

dup fork都会在不调用open时创建新的file结构(对应同一个inode)。

注:并不是每个close调用都会调用release;只有真正释放设备数据结构的close调用才会调用release。内核对每个file结构维护其被使用次数的计数器,无论是fork还是dup,都不会创建新的数据结构(只会有open创建),它们只是增加已有结构中的计数器而已。只有在file结构的计数为0时,close才会调用release

3read  ssize_t  read(struct  file  *filp,  char __user *buf,  count,  loff_t *offp)

完成工作:传入file,将count个字节数据写入用户地址buf,修改loff_t

copy_to_user()实现
返回值说明:

n  等于count:所请求的字节数读取成功;

n  返回值为正,但小于count:只读取了部分数据;

n  0:已经达到文件尾;

n  负值:出错

4write  ssize_t  write(struct  file  *filp,  char  __user *buf,  count,  offp);

copy_from_user()实现

返回值同上。

 

1)驱动代码
Demo.h
#ifndef _DEMO_H_
#define _DEMO_H_
#include <linux/ioctl.h>
/*Macros to help debuging*/
#undef PDEBUG
#ifdef DEMO_DEBUG
	#ifdef __KERNEL__
		#define PDEBUG(fmt, args...) printk(KERN_DEBUG "DEMO:" fmt,## args) 
	#else
		#define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
	#endif
#else
#define PDEBUG(fmt, args...) 
#endif

#define DEMO_MAJOR 224
#define DEMO_MINOR 0
#define COMMAND1 1
#define COMMAND2 2

struct demo_dev {
	struct cdev cdev;
};

ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
loff_t demo_llseek(struct file *filp, loff_t off, int whence);
int demo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

#endif
demo.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/cdev.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <linux/ctype.h>
#include <linux/pagemap.h>
#include "demo.h"

MODULE_AUTHOR("Yangjin");
MODULE_LICENSE("Dual BSD/GPL");

struct demo_dev *demo_devices;

static unsigned char demo_inc = 0;//全局变量,每次只能打开一个设备

static u8 demo_buffer[256];

int demo_open(struct inode *inode, struct file *filp)
{
	struct demo_dev *dev;
	
	if (demo_inc > 0) return -ERESTARTSYS;
	demo_inc++;
	dev = container_of(inode->i_cdev, struct demo_dev, cdev);
 	filp->private_data = dev;

    return 0;
}

int demo_release(struct inode *inode, struct file *filp)
{	
	demo_inc--;
	return 0;
}

ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	int result;
	loff_t pos = *f_pos; //pos: offset

	if (pos >= 256) {
		result = 0;
		goto out;	                                                                                           
	}
	if (count > (256 - pos))
		count = 256 - pos;
	pos += count;

	if (copy_to_user(buf, demo_buffer+*f_pos, count)) {
		count = -EFAULT;
		goto out;	
	}
    
	*f_pos = pos;
out:
	return count;
}

ssize_t  demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	ssize_t retval = -ENOMEM;
	loff_t pos = *f_pos;

	if (pos > 256)
		goto out;
	if (count > (256 - pos)) 
		count = 256 - pos;	
	pos += count;
	if (copy_from_user(demo_buffer+*f_pos, buf, count)) {
		retval = -EFAULT;
		goto out;	
	}
	
	*f_pos = pos;
	retval = count;
out:
	return retval;
}

int  demo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	if (cmd == COMMAND1) {
		printk("ioctl command 1 successfully\n");
		return 0;	
	}
	if (cmd == COMMAND2) {
		printk("ioctl command 2 successfully\n");
		return 0;	
	}
	printk("ioctl error\n");
	return -EFAULT;
}

loff_t demo_llseek(struct file *filp, loff_t off, int whence)
{
	loff_t pos;
	
	pos = filp->f_pos;
	switch (whence) {
	case 0:
		pos = off;
		break;
	case 1:
		pos += off;
		break;
	case 2:
	default:
		return -EINVAL; 
	}
	
	if ((pos > 256) || (pos < 0))
		return -EINVAL;
	
	return filp->f_pos = pos;
}

struct file_operations demo_fops = {
	.owner = THIS_MODULE,
	.llseek = demo_llseek,
	.read = demo_read,
	.write = demo_write,
	.ioctl = demo_ioctl,
	.open = demo_open,
	.release = demo_release,
};

void demo_cleanup_module(void)
{
	dev_t devno = MKDEV(DEMO_MAJOR, DEMO_MINOR);
	
	if (demo_devices) {
		cdev_del(&demo_devices->cdev);
		kfree(demo_devices);
	}
	unregister_chrdev_region(devno, 1);
}

Init module流程:
1)注册设备号MKDEV;
2)注册设备驱动程序,即初始化cdev结构(嵌入到demo_devices结构中)
int demo_init_module(void)
{
	int result;
	dev_t dev = 0;
	
	dev = MKDEV(DEMO_MAJOR, DEMO_MINOR);
	result = register_chrdev_region(dev, 1, "DEMO");
	if (result < 0) {
		printk(KERN_WARNING "DEMO: can't get major %d\n", DEMO_MAJOR);
		return result;
	}
	demo_devices = kmalloc(sizeof(struct demo_dev), GFP_KERNEL);
	if (!demo_devices) {
		result = -ENOMEM;
		goto fail;
	}
	memset(demo_devices, 0, sizeof(struct demo_dev));
	cdev_init(&demo_devices->cdev, &demo_fops);	
demo_devices->cdev.owner = THIS_MODULE;
	demo_devices->cdev.ops = &demo_fops; //将创建的字符设备与file_operations中各函数操作连接起来

	result = cdev_add(&demo_devices->cdev, dev, 1);
	if (result) {
		printk(KERN_NOTICE "error %d adding demo\n", result);
		goto fail;
	}
	return 0;
fail:
	demo_cleanup_module();
	return result;
}

module_init(demo_init_module);
module_exit(demo_cleanup_module);


 

2)加载驱动insmod demo.ko,再使用lsmodcat /proc/modules查看驱动是否安装;

3)创建设备节点:mknod  /dev/yangjin c 224 0;注意:此处的节点设备号要与驱动程序中的注册的设备号相同。

4)再编写应用程序测试代码:

用户测试代码:

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/rtc.h>
#include <linux/ioctl.h>
#include <stdio.h>
#include <stdlib.h>

#define COMMAND1 1
#define COMMAND2 2

int main()
{
	int fd;
	int i;
	char data[256] = {0};
	int retval;
	
	fd = open("/dev/yangjin", O_RDWR);
	if (fd == 1) {
		perror("open error\n");
		exit(-1);
	}
	printf("open /dev/yangjin successfully\n");
	retval = ioctl(fd, COMMAND1, 0);
	if (retval == -1) {
		perror("ioctl error\n");
		exit(-1);
	}
	printf("ioctl command 1 successfully\n");
	retval = write(fd, "yangjin", 7);
	if (retval == -1) {
		perror("write error\n");
		exit(-1);
	}
	retval = lseek(fd, 0, 0);
	if (retval == -1) {
		perror("lseek error\n");
		exit(-1);
	}
	retval = read(fd, data, 10);
	if (retval == -1) {
		perror("read error\n");
		exit(-1);
	}
	printf("read successfully: %s\n", data);
	close(fd);
	return 0;
}


 

 

你可能感兴趣的:(linux,驱动)