Chinaunix首页 | 论坛 | 博客
  • 博客访问: 672081
  • 博文数量: 134
  • 博客积分: 3158
  • 博客等级: 中校
  • 技术积分: 1617
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-30 22:36
文章分类

全部博文(134)

文章存档

2012年(2)

2011年(28)

2010年(68)

2009年(35)

2008年(1)

我的朋友

分类: LINUX

2009-10-19 17:56:04

一个2.6内核字符设备驱动hello world 注释超详细
2008-07-17 17:02
本例是冯国进的 《嵌入式Linux 驱动程序设计从入门到精通》的第一个例子
感觉真是好书   强烈推荐
注释是deep_pro加的 转载请注明!我的特点是文不加点!
这个驱动是在内存中分配一个256字节的空间,供用户态应用程序读写。

先是头文件 demo.h


#ifndef _DEMO_H_
#define _DEMO_H_

#include /* needed for the _IOW etc stuff used later */

/********************************************************
* Macros to help debugging
********************************************************/
#undef PDEBUG             /* undef it, just in case */
#ifdef DEMO_DEBUG
#ifdef __KERNEL__
#    define PDEBUG(fmt, args...) printk( KERN_DEBUG "DEMO: " fmt, ## args)
#else//usr space
#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#endif
#else
# define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif

#undef PDEBUGG
#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */

//设备号
#define DEMO_MAJOR 224
#define DEMO_MINOR 0
#define COMMAND1 1
#define COMMAND2 2

//自己定义的设备结构
struct DEMO_dev
{
    struct cdev cdev;    /* Char device structure        */
};

//函数申明 原来Linux驱动程序设计这么简单 只需要实现这么几个函数就可以了
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_H_ */

然后是demo.c
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2007, 2010 fengGuojin(fgjnew@163.com)
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include "demo.h"

MODULE_AUTHOR("fgj");
MODULE_LICENSE("Dual BSD/GPL");
//很郁闷的是就算这样 移植到开发板上仍然说这个驱动污染了内核

struct DEMO_dev *DEMO_devices;
//申明自己定义的设备结构的指针
static unsigned char demo_inc=0;
//计数 记录此设备被打开的次数
static u8 demoBuffer[256];
//内核里的数据结构和数据类型还真是诡异啊 这个u8还真费解 原来是无符号8位的数据类型
//

int DEMO_open(struct inode *inode, struct file *filp)
{
    struct DEMO_dev *dev;
//只允许打开设备一次
    if(demo_inc>0)return -ERESTARTSYS;
    demo_inc++;
/*container_of 宏 通过结构中的某个变量获取结构本身的指针
%CE%DA%D1%BB%C3%F7/blog/item/b805ae1975f02e4443a9ade3.html
真是太高级了 我看不懂
*/
    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; /* 文件的读写位置 */
    if(pos>=256)
    {
        result=0;
        goto out;
    }
    if(count>(256-pos))
    {
        count=256-pos;
    }
    pos += count;
   
    if (copy_to_user(buf,demoBuffer+*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; /* value used in "goto out" statements */
    loff_t pos= *f_pos;
    if(pos>=256)
    {
         goto out;
    }
//如果要写入的输入大于剩下的内存空间 就只写入剩下的空间数量 以防溢出
    if(count>(256-pos))
    {
        count=256-pos;
    }
//剩下的代码 如果c语言扎实的话 ,不难看懂 因为我的表述能力不好 见谅
    pos += count;
//copy_from_user 原文说将数据复制到用户空间 ,我觉得应该是将数据复制到内核空间,应该是作者笔误,没什么大不了的
    if (copy_from_user(demoBuffer+*f_pos, buf, count)) {
        retval = -EFAULT;
        goto out;
    }

    *f_pos = pos;
    return count;
out:
    return retval;
}

/*我是这几天才大致知道ioctl的作用 ioctl是用来控制设备的 ,unsigned int cmd就是发给设备的命令
ioctl()或许是Linux下最庞杂的函数*/
int DEMO_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
{
    if(cmd==COMMAND1)
    {
           printk("ioctl command1 successfully\n");
           return 0;
    }
    if(cmd==COMMAND2)
    {
           printk("ioctl command2 successfully\n");
           return 0;
    }
   
    printk("ioctl error\n");
          return -EFAULT;
}

//llseek 实现随即存取 ,loff_t应该是一个无符号整型 记录文件指针偏移量的 这段代码参考用户态的seek()就不难理解
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;
}

//file_operations这个结构体真是相当重要 需要搞清楚它的作用
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,
};

/*******************************************************
                MODULE ROUTINE
*******************************************************/

void DEMO_cleanup_module(void)
{
//在下面的入口函数有讲MKDEV
    dev_t devno = MKDEV(DEMO_MAJOR, DEMO_MINOR);

    if (DEMO_devices)
    {
        cdev_del(&DEMO_devices->cdev);
//内核下的内存操作函数还真怪异 习惯就好
        kfree(DEMO_devices);
    }
//调用unregister_chrdev_region()函数释放分配的一系列设备号
    unregister_chrdev_region(devno,1);
}

int DEMO_init_module(void)
{
    int result;
// 在内核中,dev_t类型(在中定义)用来保存设备编号——包括主设备号和次设备号
    dev_t dev = 0;

/*内核中定义了三个宏来处理主、次设备号:MAJOR和MINOR宏可以从16位数中提取出主、次设备号,而MKDEV宏可以把主、此号合并为一个16位数。
高8位用于主设备号,低8位用于次设备号。
%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AB%A0%20%20%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F/11.2.3.htm*/

    dev = MKDEV(DEMO_MAJOR, DEMO_MINOR);

/*获取一个或多个设备编号来使用
如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域. */

    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了 只能大概猜个意思
//还有就是 struct file_operations , 这个结构体的作用搞清楚了 就明白为什么Linux 驱动只要实现很少的驱动就可以了
    cdev_init(&DEMO_devices->cdev, &DEMO_fops);
    DEMO_devices->cdev.owner = THIS_MODULE;
    DEMO_devices->cdev.ops = &DEMO_fops;
//在内核中添加字符驱动
    result = cdev_add (&DEMO_devices->cdev, dev, 1);
    if(result)
    {
        printk(KERN_NOTICE "Error %d adding DEMO\n", result);
        goto fail;
    }

    return 0;

fail:
//失败了 就调用出口函数擦pp走人了
    DEMO_cleanup_module();
    return result;
}
//这两个是内核驱动必备的 指明程序入口和出口
module_init(DEMO_init_module);
module_exit(DEMO_cleanup_module);
demo.c文件结束

make文件我还不会写 我使用kdevelop建立内核工程来编译这个驱动
下面这个makefile是编译移植到arm9 开发板(s3c2440) 的ko文件用的
AR= ar
ARCH = arm
CC = arm-linux-gcc
CFLAGS += $(DEBFLAGS) -Wall
CFLAGS += -I$(LDDINC)
LDFLAGS = -Xlinker -rpath-link /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1


ifneq ($(KERNELRELEASE),)
obj-m:= demo-driver.o #模块名称
else
KDIR:= /root/linux-2.6.12
PWD:= $(shell pwd)
default:
    $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
endif



加载驱动
# insmod demo.ko
然后使用lsmod 或 cat /proc/modules查看驱动是否加载

# mknod /dev/fgj c 224 0 创建设备节点

然后就可以使用下面代码来测试驱动

/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2007, 2010 fengGuojin(fgjnew@163.com)
*/
#include
#include
#include
#include
#include
#include
#include

#define COMMAND1 1
#define COMMAND2 2

main()
{
int fd;
int i;
char data[256];

int retval;
fd=open("/dev/fgj",O_RDWR);
if(fd==-1)
{
     perror("error open\n");
     exit(-1);
}
printf("open /dev/smbus successfully\n");
retval=ioctl(fd,COMMAND1,0);
if(retval==-1)
{
    perror("ioctl error\n");
    exit(-1);
}
printf("send command1 successfully\n");

retval=write(fd,"fgj",3);
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,3);

if(retval==-1)
{
    perror("read error\n");
    exit(-1);
}
   printf("read successfully:%s\n",data);
close(fd);
}

好了 对源程序的认真阅读 ,初步了解了字符驱动程序的编写
阅读(1104) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~