Linux字符設備驅動
1. 字符設備驅動的組成
在Linux系統中,字符設備驅動由如下幾部分組成:設備相關結構體、字符設備驅動模塊的加載與卸載函數、字符設備驅動中file_operations的成員函數。
1)設備相關結構體
在Linux驅動程序中,工程師一般習慣爲相關設備建立一個結構體,其中包含cdev、私有數據、信號量等信息,如下:
<pre name="code" class="cpp">/*驅動結構體*/
struct xxx_dev_t{
struct cdev cdev;
......
}xxx_dev;
2) 字符設備驅動模塊的加載與卸載函數
加載函數的功能爲:實現cdev結構體的註冊和設備號的申請、設備的初始化等;卸載函數功能:相應結構體的註銷和設備號的註銷、資源的釋放等;
加載
/***********************************************************************
* Function Name: globalmem_init
* Paramter: type name [IN]: void
* Function Descrition:
* Module init
* Return: 0
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
static int globalmem_init(void)
{
printk(KERN_INFO " Globlamem Driver for init!\n");
/*init cdev*/
...
/*register dev no*/
...
return 0;
}
卸載
/***********************************************************************
* Function Name: globalmem_exit
* Paramter: type name [IN]: void
* Function Descrition:
* Module exit
* Return: void
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
static void globalmem_exit(void)
{
printk(KERN_INFO " Globlamem Driver for exit!\n");
/*release dev no*/
...
/*del cdev*/
...
}
3)file_operations結構體
該結構體是字符設備驅動的主體內容,是Linux驅動與應用程序及與底層處理的橋樑。
4)字符設備驅動程序的結構
字符設備驅動程序的結構,如下圖所示。
2. globalmem虛擬設備驅動實例
1) 頭文件及宏定義
#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 <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
/*struct mem*/
#define GLOBALMEM_SIZE 0x1000 /*全局內存最大4K字節*/
#define MEM_CLEAR 0x1 /*清0全局內存*/
#define GLOBALMEM_MAJOR 254 /*預設的globalmem的主設備號*/
2)結構體定義
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem struct*/
struct globalmem_dev{
struct cdev cdev; /*cdev struct*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局內存,私有數據*/
};
/*dev struct piont*/
struct globalmem_dev *globalmem_devp = NULL;
3)函數主體
/*文件打開函數*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*將設備結構體指針賦值給文件私有數據指針*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件釋放函數*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl設備控制函數 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*獲得設備結構體指針*/
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
/*讀函數*/
static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*內核空間->用戶空間*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
}
return ret;
}
/*寫函數*/
static ssize_t globalmem_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 globalmem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
/*用戶空間->內核空間*/
if (copy_from_user(dev->mem + p, buf, count) )
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
return ret;
}
/* seek文件定位函數 */
static loff_t globalmem_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 > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相對文件當前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_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;
}
/*文件操作結構體*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/***********************************************************************
* Function Name: globalmem_setup_cdev
* Paramter:
* struct globalmem_dev *dev [INOUT];
* int index [IN];
* Function Descrition:
* cdev init and register.
* Return: void
* Author: Vinvian 2014/11/11
***********************************************************************/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err = 0;
int devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops); //dev init
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/***********************************************************************
* Function Name: globalmem_init
* Paramter: type name [IN]: void
* Function Descrition:
* Module init
* Return: 0
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
static int globalmem_init(void)
{
printk(KERN_INFO " Globlamem Driver for init!\n");
int result;
dev_t devno = MKDEV(globalmem_major, 0); //Create det_t with major and minor devNo
/*Regsiter devNo*/
if (globalmem_major) //has major no
result = register_chrdev_region(devno, 1, "globalmem");
else //no major no
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*malloc dev struct mem*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申請失敗*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*init cdev*/
globalmem_setup_cdev(globalmem_devp, 0);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/***********************************************************************
* Function Name: globalmem_exit
* Paramter: type name [IN]: void
* Function Descrition:
* Module exit
* Return: void
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
void globalmem_exit(void)
{
/*release cdev*/
cdev_del(&globalmem_devp->cdev);
/*release struct dev mem*/
kfree(globalmem_devp);
/*release devNo*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
4)
module_init(globalmem_init);
module_exit(globalmem_exit);
module_param(globalmem_major, int, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Vinvian");
MODULE_DESCRIPTION("A globalmem driver Module");
MODULE_ALIAS("A globalmem driver");
3. 擴展學習
1)cdev_init();
2) cdev_add();
3) 宏MKDEV();
4. 參考文獻
1)Linux設備驅動開發詳解 宋寶華著