标签:style blog http color io os 使用 ar strong
0.前言
研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧!
1.准备工作
a)查看内核版本
uname -r
b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html)
在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils;
解压方法示例:xz -d linux-3.1-rc4.tar.xz
tar -xf linux-3.1-rc4.tar
c)安装内核函数man手册
编译 make mandocs; 安装 make installmandocs; 测试 man printk。
2.对字符驱动的简单认识
a)我所理解的驱动程序就是使用Linux内核函数编写一个内核模块,实现对设备文件的打开,关闭,读写,控制等操作,这要对设备文件结构体的构成有深入的了解,让人宽慰的是驱动程序基本框架不变,难点就是程序要涉及并发控制,内存分配,中断等问题,因此会较为复杂,所以任重而道远呀。
b)三个重要的数据结构:file_operations结构体里面定义的主要是各种函数指针,通过定义设备的一系列函数,再将函数指针赋值,就完成了设备和函数的关联,驱动程序会实现系统调用到实际硬件设备的操作的映射;file结构体表示一个打开的文件描述符,里面有打开的标志(只读只写),文件指针等等,该结构体和一个打开的设备文件相对应;inode结构体主要是用来和硬盘上的文件意义对应,硬盘上每新建一个文件都对应一个inode节点,包括inode号,块号,大小等信息(不懂时看这个,阮一峰,理解inode:http://www.ruanyifeng.com/blog/2011/12/inode.html),查看他们的位置在 /usr/src/linux-3.**.pae/include/linux/fs.h 中。
c) Linux内核驱动模块的剖析,这篇文章讲了模块加载到内核的具体过程。
http://www.ibm.com/developerworks/cn/linux/l-lkm/
d)设备文件:实现对硬件设备的抽象,使得对硬件的读写等价于对设备文件的读写。
e)主设备号和次设备号:主设备号用来表示不同种类的设备,次设备号主要用来区分设备。(http://blog.csdn.net/gqb_driver/article/details/8805179)。
3.测试HelloWorld模块
a)源码
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void){
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void){
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
b)Makefile
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
c)加载和卸载,这里要切换到命令行模式(ctrl Alt f1 切换到 命令行模式, ctrl alt f7 切换到图形界面模式)才会显示出结果,否则需要在 /var/log/syslg 中查看。
4.一个简单的字符设备驱动程序
a)程序源码 scull.h scull.c
#ifndef __SCULL_H__
#define __SCULL_H__
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <asm/uaccess.h>
#define SCULL_MAJOR 0
#define SCULL_NR_DEVS 4
#define SCULL_QUANTUM 100
#define SCULL_QSET 10
#define SCULL_IOC_MAGIC ‘C‘
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC, 10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
#define SCULL_IOC_MAXNR 12
struct scull_qset{
void **data; //量子集数组
struct scull_qset *next;
};
struct scull_dev{
struct scull_qset *data; //量子集链表指针
int quantum; //量子集大小
int qset; //量子集数组的大小
unsigned long size; //数据的大小
struct semaphore sem;
struct cdev cdev;
};
int scull_init_module(void);
void scull_cleanup_module(void);
int scull_open(struct inode *, struct file *);
int scull_release(struct inode *, struct file *);
loff_t scull_llseek(struct file *, loff_t, int);
long scull_ioctl(struct file *, unsigned int, unsigned long);
ssize_t scull_read(struct file *, char __user *, size_t, loff_t*);
ssize_t scull_write(struct file *, const char __user*, size_t, loff_t *);
void scull_setup_cdev(struct scull_dev *, int);
int scull_trim(struct scull_dev *);
struct scull_qset *scull_follow(struct scull_dev *, int);
#endif
#include "scull.h"
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
struct scull_dev *scull_devices;
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
int scull_open(struct inode *inode, struct file *filp){
struct scull_dev *dev;
dev = container_of(inode->i_cdev,struct scull_dev, cdev);
filp->private_data = dev;
printk(KERN_WARNING "In OPen\n");
if((filp->f_flags & O_ACCMODE) == O_WRONLY){
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
scull_trim(dev);
up(&dev->sem);
}
return 0;
}
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
loff_t scull_llseek(struct file *filp, loff_t off, int whence){
struct scull_dev *dev = filp->private_data;
loff_t newpos = 0;
switch(whence){
case 0: //SEEK_SET
newpos = off;
break;
case 1: //SEEK_CUR
newpos += off;
break;
case 2: //SEEK_END
newpos += dev->size + off;
break;
default:
return -EINVAL;
}
if(newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
ssize_t scull_read(struct file* filp, char __user *buf, size_t count, loff_t *f_pos){
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(*f_pos >= dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_pos;
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
dptr = scull_follow(dev, item);
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;
if(count > quantum - q_pos)
count = quantum - q_pos;
if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum * qset;
int item, rest, s_pos, q_pos;
ssize_t retval = -ENOMEM;
printk(KERN_INFO "before down_interruptible!\n");
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
dptr = scull_follow(dev, item);
if(dptr == NULL)
goto out;
printk(KERN_INFO "before kmalloc!\n");
if(!dptr->data){
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if(!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(!dptr->data[s_pos])
goto out;
}
if(count > quantum - q_pos)
count = quantum - q_pos;
if(copy_from_user(dptr->data[s_pos] + q_pos, buf, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
if(dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
int err = 0, tmp;
int retval = 0;
if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
if(_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
if(_IOC_DIR(cmd) & _IOC_READ)
err = ! access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if(_IOC_DIR(cmd) & _IOC_WRITE)
err = ! access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if(err)
return -EFAULT;
switch(cmd){
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user*)arg); // arg 是个指针
break;
case SCULL_IOCTQUANTUM:
if(!capable(CAP_SYS_ADMIN)) // arg 是个数值
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM:
retval = __put_user(scull_quantum, (int __user*)arg);
break;
case SCULL_IOCQQUANTUM:
return scull_quantum;
case SCULL_IOCXQUANTUM:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user*)arg);
if(retval == 0)
retval = __put_user(tmp, (int __user*)arg);
break;
case SCULL_IOCHQUANTUM:
if(!capable(CAP_SYS_ADMIN))
return EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return arg;
case SCULL_IOCSQSET:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user*)arg);
break;
case SCULL_IOCTQSET:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user*)arg);
break;
case SCULL_IOCQQSET:
return scull_qset;
case SCULL_IOCXQSET:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user*)arg);
if(retval == 0)
retval = __put_user(tmp, (int __user*)arg);
break;
case SCULL_IOCHQSET:
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
default:
return -ENOTTY;
}
return retval;
}
struct scull_qset *scull_follow(struct scull_dev *dev, int n){
struct scull_qset *qs = dev->data;
if(!qs){
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL) ;
if(qs == NULL)
return NULL;
memset(qs, 0, sizeof(struct scull_qset));
}
while(n--){
if(!qs->next){
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if(qs->next == NULL)
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
void scull_setup_cdev(struct scull_dev *dev, int index){
int err, devno = MKDEV(scull_major, scull_minor + index); //设备号
//1.初始化字符设备
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
//2.把该字符设备添加到内核
err = cdev_add(&dev->cdev, devno, 1);
if(err)
printk(KERN_NOTICE "Errno %d adding scull %d", err, index);
}
int scull_trim(struct scull_dev *dev){ //清空scull的data数据域
struct scull_qset *next, *dptr;
int q_set = dev->qset;
int i;
for(dptr = dev->data; dptr; dptr = next){
if(dptr->data){
for(i = 0; i < q_set; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
void scull_cleanup_module(void){
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
//1.释放内存空间
if(scull_devices){
for(i = 0; i < scull_nr_devs; i++){
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
printk(KERN_WARNING "rmmod module!");
//2.释放设备号
unregister_chrdev_region(devno, scull_nr_devs);
}
int scull_init_module(void){
int result, i; //返回值 索引
dev_t dev = 0; //设备号
//1.申请设备号
if(scull_major){ //已知主设备号
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
}
else{
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); //不知道主设备号
scull_major = MAJOR(dev); //内核动态分配合适的
}
if(result < 0){
printk(KERN_WARNING "scull:can‘t get major %d\n", scull_major);
return result;
}
//2.为设备申请内存空间
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if(!scull_devices){
result = -ENOMEM;
goto fail;
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
//3.初始化每个设备
for(i = 0; i < scull_nr_devs; i++){
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
sema_init(&scull_devices[i].sem, 1);
scull_setup_cdev(&scull_devices[i], i); //初始化字符设备结构体
}
return 0;
fail:
scull_cleanup_module();
return result;
}
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
MODULE_AUTHOR("Monica Lee");
MODULE_LICENSE("Dual BSD/GPL");
module_init(scull_init_module);
module_exit(scull_cleanup_module);
c)测试文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char buffer[20] = "Hello World!";
int fd, count;
// 向字符设备中写数据
fd = open("/dev/scull0", O_WRONLY);
if(fd == -1)
perror("Open failed");
count = write(fd, buffer, strlen(buffer));
if(count == -1)
perror("Write failed");
else
printf("Write count :%d\n", count);
close(fd);
//从字符设备中读数据
memset(buffer, 0, sizeof buffer);
fd = open("/dev/scull0", O_RDONLY);
if(fd == -1)
perror("Open failed");
count = read(fd, buffer, sizeof buffer);
if(count == -1)
perror("Read failed");
printf("Read data: %s\n", buffer);
close(fd);
return 0;
}
d)编译和执行过程(sudo模式)
make
insmod加载到内核;
cat /proc/devices 查看主设备号;
mknod /dev/scull0 c 250 0 创建设备文件;
执行测试程序;
rmmod 卸载模块。
e)调试过程中遇到的错误:
最新版本的内核中 ioctl 函数和init_MUTEX 函数都不存在了,原先的 int (*ioctl)(struct inode*, struct file*, unsigned int, unsigned long);被改为了long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);init_MUTEX函数,使用sema_init(sem, 1)代替。
0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序
标签:style blog http color io os 使用 ar strong
原文地址:http://www.cnblogs.com/monicalee/p/3959283.html