天天看點

linux驅動學習(四) linux字元裝置驅動 cdev

下面開始學習linux字元裝置驅動,也是linux驅動中最簡單的驅動子產品。

在記憶體中虛拟出一段空間作為字元裝置,并為之編寫些列的驅動程式。

字元裝置驅動cdev中用到的兩個重要的結構體如下,現補充下基本知識

一、cdev

/*
*核心源碼位置
*linux2.6.38/include/linux/cdev.h
*/

struct cdev {
	struct kobject kobj;
	struct module *owner;   //一般初始化為:THIS_MODULE
	const struct file_operations *ops;   //字元裝置用到的例外一個重要的結構體file_operations,cdev初始化時與之綁定
	struct list_head list;
	dev_t dev;  //主裝置号24位 與次裝置号8位,dev_t為32位整形
	unsigned int count;
};
           

二、file_operations

熟悉c語言檔案程式設計的應該知道 read write等函數,這些函數都在file_operations中聲明,在read等函數中實作與硬體相關的操作,這樣就讓具體的硬體裝置與作業系統聯系在了一起

/*
~/include/linux/fs.h
*/

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 *);
	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 *, 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 (*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 (*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);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};
           

光有這cdev與file_operations定義的結構體變量是不行的,顯然要讓他們做一些初始化工作,然後通過某個函數,讓我們定義的這兩個結構體變量與核心聯系在一起,是以,調用核心的下列函數

檔案名:char_dev.c

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}
           

為cdev開辟記憶體空間,然後,将file_operations定義的變量fops指派給cdev中的ops成員變量,這樣,他們就緊密的連在一起了

以上才僅僅将cdev初始化,還未将其真正的添加到系統核心中,是以調用下列函數:

檔案名:char_dev.c

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
           

有了以上的字元裝置基礎時候後,在開始看一下字元裝置的基本結構,其實就是在hello word的基礎之上添加了裝置讀、寫、控制的函數。

頭檔案:一般包含下面幾個

#include<linux/cdev.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
           

定義的cdev結構體與裝置空間資料

/*習慣上将内部資料空間與cdev 綁定,與其封裝*/
struct mychar_dev{
	struct cdev cdev;
	unsigned char mem[MYCHAR_MEM_SIZE];
};

/*一個執行個體*/
struct mychar_dev* mychar_devp;
           

然後是讀、寫、ioctl函數的實作

/*實作file_operations結構體體的open函數*/
int mychar_open(struct inode *inode,struct file * filp)
           

int mychar_release(struct inode *inode,struct file* filp);

ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos );

ssize_t mychar_write(struct file *filp ,const char __user *buf,size_t size,loff_t *ppos);

static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig);

int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg);

以上函數實作後将裡指派到file_operations中相應的函數成員變量

static const struct file_operations mychar_fops = {
	.owner = THIS_MODULE,
	.llseek = mychar_llseek,
	.read = mychar_read,
	.write = mychar_write,
	.ioctl = mychar_ioctl,
	.open = mychar_open,
	.release =mychar_release,
};
           

最後是init 與exit函數

static int __init mychar_init(void);

static void __exit mychar_exit(void);

最後是

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

module_param(mychar_major,int,S_IRUGO);

module_init(mychar_init);
module_exit(mychar_exit);
           

下面看一下完整的源碼:

/*在記憶體中申請1k 大小的記憶體做為簡單的一個裝置來通路*/
/*一般包含的頭檔案*/
#include<linux/cdev.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>

/*裝置空間*/
#define MYCHAR_MEM_SIZE 0x0400
/*主裝置号*/
#define MYCHAR_MAJOR	260
/*自定義的清除記憶體的指令*/
#define MYCHAR_MEN_CLR	0x01
/*主裝置号變量*/
static int mychar_major = MYCHAR_MAJOR;

/*習慣上将内部資料空間與cdev 綁定,與其封裝*/
struct mychar_dev{
	struct cdev cdev;
	unsigned char mem[MYCHAR_MEM_SIZE];
};

/*一個執行個體*/
struct mychar_dev* mychar_devp;

/*實作file_operations結構體體的open函數*/
int mychar_open(struct inode *inode,struct file * filp)
{
	filp->private_data = mychar_devp;
	return 0;
}
/*同上*/
int mychar_release(struct inode *inode,struct file* filp)
{
	return 0;
}

/*read*/
ssize_t mychar_read(struct file *filp,char __user *buf,size_t size ,loff_t *ppos )
{
	unsigned long p=*ppos;
	unsigned int count = size;
	int ret = 0;
	struct mychar_dev *dev = filp->private_data;
	if(p>MYCHAR_MEM_SIZE)
		return 0;
	if(count > MYCHAR_MEM_SIZE-p)
		count = MYCHAR_MEM_SIZE-p;
	if( copy_to_user(buf,(void*)(dev->mem+p),count)){
		ret= -EFAULT;
	}else{
		*ppos +=count;
		ret = count;
		printk(KERN_INFO "read %u bytes(s) from %1u\n",count,p);
	}
	return ret;
}
/*write*/
ssize_t mychar_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;
	struct mychar_dev *dev = filp->private_data;
	if(p > MYCHAR_MEM_SIZE)
		return 0;
	if(count > MYCHAR_MEM_SIZE-p)
		count = MYCHAR_MEM_SIZE-p;
	if(copy_from_user((void*)(dev->mem),buf,count)){
		ret = -EFAULT;
	}else{
		*ppos +=count;
		ret = count;
		printk(KERN_INFO "written %u byte(s) from %1u\n",count,p);
	}
	return ret;
}
/*llseek*/
static loff_t mychar_llseek(struct file *filp,loff_t offset,int orig)
{
	loff_t ret = 0;
	switch(orig){
	case 0:	/*相對于檔案開始位置偏移*/
		if(offset < 0)
			ret = -EINVAL;
		break;
		if((unsigned int)offset > MYCHAR_MEM_SIZE){
			ret = -EINVAL;
			break;
		}
		filp->f_pos =(unsigned int )offset;
		ret = filp->f_pos;
		break;
	case 1:	/*相對于檔案目前位置*/
		if((filp->f_pos+offset)>MYCHAR_MEM_SIZE){
			ret = -EINVAL;
			break;
		}
		if((filp->f_pos+offset)< 0){
			ret = -EINVAL;
			break;
		}
		filp->f_pos +=offset;
		ret = filp->f_pos;
		break;
	default:
		ret = - EINVAL;
		break;
	}
	return ret;
}
/*ioctl*/
int mychar_ioctl(struct inode * inodep ,struct file *filp ,unsigned int cmd ,unsigned long arg)
{
	struct mychar_dev *dev =filp->private_data;
	switch(cmd){
	case MYCHAR_MEM_CLR:
		memset(dev->mem,0,MYCHAR_MEM_SIZE);
		printk(KERN_INFO "mychar memery is set to zero\n");
		break;
	default:
		return -EINVAL;
	}
	return 0;
}
static const struct file_operations mychar_fops = {
	.owner = THIS_MODULE,
	.llseek = mychar_llseek,
	.read = mychar_read,
	.write = mychar_write,
	.ioctl = mychar_ioctl,
	.open = mychar_open,
	.release =mychar_release,
};

/*cdev結構初始化*/
static void mychar_setup_cdev(struct mychar_dev *dev,int index)
{
	int err;
	int devno = MKDEV(mychar_major,index);
	cdev_init(&dev->cdev,&mychar_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev,devno,1);
	if(err)
		printk(KERN_NOTICE " Error %d adding mychar %d",err,index);

}


/*init*/
static int __init mychar_init(void)
{
	int result;
	dev_t devno = MKDEV(mychar_major,0);
	if(mychar_major)
		result = register_chrdev_region(devno,1,"mychar");
	else{
		result = alloc_chrdev_region(&devno,0,1,"mychar");
		mychar_major = MAJOR(devno);
	}
	if(result<0)
		return result;
	mychar_devp = kmalloc(sizeof(struct mychar_dev),GFP_KERNEL);
	if(!mychar_devp){
		result = -ENOMEM;
		goto fall_malloc;

	}
	memset(mychar_devp,0,sizeof(struct mychar_dev));
	mychar_setup_cdev(mychar_devp,0);
	return 0;

fall_malloc:
	unregister_chrdev_region(devno,1);
	return result;
}

/*exit*/
static void __exit mychar_exit(void)
{
	cdev_del(&mychar_devp->cdev);
	kfree(mychar_devp);
	unregister_chrdev_region(MKDEV(mychar_major,0),1);
}


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

module_param(mychar_major,int,S_IRUGO);

module_init(mychar_init);
module_exit(mychar_exit);
           

我個人覺得,如果不太會使用vim的ctag功能,可以在windows平台下source insight中編寫linux驅動。

隻要事先先建立一個linux核心源碼的工程,在在這個工程中添加自己的linux驅動程式源碼。

這樣就實作了一個IDE,函數、變量、宏定義不全功能,文法着色等等非常友善,加快開發速度。

繼續閱讀