分享给大家一个嵌入式linux驱动的入门程序解析(源代码)

/*======================================================================
    A globalmem driver as an example of char device drivers 
  
    The initial developer of the original code is Baohua Song
    <author@linuxdriver.cn>. All Rights Reserved.
======================================================================*/
#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>

#define GLOBALMEM_SIZE 0x1000 /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1  /*清0全局内存*/
#define GLOBALMEM_MAJOR 254    /*预设的globalmem的主设备号*/

static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev                                    
{                                                       
  struct cdev cdev; /*cdev结构体*/                      
  unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/       
};

struct globalmem_dev *globalmem_devp; /*设备结构体指针*/

/*文件打开函数*/
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; //ENXIO为errno中的类型
  if (count > GLOBALMEM_SIZE - p)
    count = GLOBALMEM_SIZE - p;

  /*内核空间->用户空间
  *其中buf为用户的空间,(void*)(dev->mem + p)为内核空间,count为拷贝的数量 
  */
  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 %d/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,
};

/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
  int err, devno = MKDEV(globalmem_major, index);

  cdev_init(&dev->cdev, &globalmem_fops);
  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);
}
/*
cdev_init() — initialize a cdev structure
-------------------------------------------------
linux-2.6.22/include/linux/cdev.h

void cdev_init (struct cdev                  *cdev,
                const struct file_operations *fops);

    @cdev: the structure to initialize
    @fops: the file_operations for this device
   
    Initializes cdev, remembering fops, making it ready to add to the system with cdev_add.

 

cdev_add() — add a char device to the system
-------------------------------------------------
linux-2.6.22/include/linux/cdev.h

int cdev_add (struct cdev    * p,
              dev_t          dev,
              unsigned       count);

    @p: the cdev structure for the device
    @dev: the first device number for which this device is responsible
    @count: the number of consecutive minor numbers corresponding to this device
    cdev_add() adds the device represented by @p to the system, making it live immediately. A negative error code is returned on failure.

 

alloc_chrdev_region() — register a range of char device numbers
-------------------------------------------------
linux-2.6.22/include/linux/cdev.h

int alloc_chrdev_region(dev_t            *dev,
                        unsigned int     baseminor,
                        unsigned int     count,
                        char             *name)

    @dev: output parameter for first assigned number
    @baseminor: first of the requested range of minor numbers(次设备号)
    @count: the number of minor numbers required
    @name: the name of the associated device or driver

    Allocates a range of char device numbers. The major number will be chosen dynamically, and returned (along with the first minor number) in dev. Returns zero or a negative error code.使用alloc_chrdev_region()函数动态获取主设备号。

*/

/*设备驱动模块加载函数*/
int globalmem_init(void)
{
  int result;
  dev_t devno = MKDEV(globalmem_major, 0);

  /* 申请设备号*/
  if (globalmem_major)
    result = register_chrdev_region(devno, 1, "globalmem");
  else  /* 动态申请设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
    globalmem_major = MAJOR(devno);
  } 
  if (result < 0)
    return result;
   
  /* 动态申请设备结构体的内存*/
  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));
 
  globalmem_setup_cdev(globalmem_devp, 0);
  return 0;

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

/*模块卸载函数*/
void globalmem_exit(void)
{
  cdev_del(&globalmem_devp->cdev);   /*注销cdev*/
  kfree(globalmem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}

MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");

module_param(globalmem_major, int, S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);

/*
memset用法详解(转)
2007-01-31 20:00
memest原型 (please type "man memset" in your shell)

 void *memset(void *s,  int c, size_t n);

memset:作用是在一段内存块中填充某个给定的值,
它对较大的结构体或数组进行清零操作的一种最快方法。

常见的三种错误

第一: 搞反了c 和 n的位置.

一定要记住 如果要把一个char a[20]清零, 一定是 memset(a, 0, 20)
而不是 memset(a, 20,  0)

第二: 过度使用memset, 我想这些程序员可能有某种心理阴影,
他们惧怕未经初始化的内存, 所以他们会写出这样的代码:

char buffer[20];

memset(buffer, 0, sizeof((char)*20));
strcpy(buffer, "123");

这里的memset是多余的. 因为这块内存马上就被覆盖了, 清零没有意义.

第三: 其实这个错误严格来讲不能算用错memset,
但是它经常在使用memset的场合出现

int some_func(struct something *a){
 …
 …
 memset(a, 0, sizeof(a));
 …
}

问:为何要用memset置零?memset( &Address, 0, sizeof(Address))?经常看到这样的用法,其实不用的话,分配数据的时候,剩余的空间也会置零的。

答:1.如果不清空,可能会在测试当中出现野值。 你做下面的试验看看结果()

char buf[5];

CString str,str1; //memset(buf,0,sizeof(buf))??for(int i = 0;i<5;i++)
{
 str.Format(“%d “,buf[i]);
 str1 +=str ;
}
TRACE(“%s/r/n“,str1)

2.其实不然!特别是对于字符指针类型的,剩余的部分通常是不会为0的,
不妨作一个试验,定义一个字符数组,并输入一串字符,如果不用memset实现清零,
使用MessageBox显示出来就会有乱码(0表示NULL,如果有,就默认字符结束,
不会输出后面的乱码)

问:

如下demo是可以的,能把数组中的元素值都设置成字符1,
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
    char a[5];
    memset(a,'1',5);
    for(int i = 0;i < 5;i++)
      cout<<a[i]<<"  ";
    system("pause");
    return 0;
}
而,如下程序想吧数组中的元素值设置成1,却是不可行的
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
    int a[5];
    memset(a,1,5);//这里改成memset(a,1,5 *sizeof(int))也是不可以的
    for(int i = 0;i < 5;i++)
      cout<<a[i]<<"  ";
    system("pause");
    return 0;
}
问题是:

1,第一个程序为什么可以,而第二个不行,
2,不想要用for,或是while循环来初始化int a[5];能做到吗??ㄓ忻挥幸桓鱿駇emset()这样的函数初始化)


答:

1.因为第一个程序的数组a是字符型的,字符型占据内存大小是1Byte,
而memset函数也是以字节为单位进行赋值的,所以你输出没有问题。
而第二个程序a是整型的,使用memset还是按字节赋值,这样赋值完以后?扛鍪樵氐闹凳导噬鲜?x01010101即十进制的16843009。
你看看你输出结果是否这样?

2.如果用memset(a,1,20);
就是对a指向的内存的20个字节进行赋值,每个都用ASCII为1的字符去填充,
转为二进制后,1就是00000001,占一个字节。一个INT元素是4字节,
合一起就是1000000010000000100000001,就等于16843009,
就完成了对一个INT元素的赋值了。
 

*/


/*
  最近在和同学们一起学习Linux驱动开发方面的知识,
  在学习过程中经常会遇到一些关于内核的知识,牵扯到一些数据结构和函数,
  在遇到时我们没有绕过去,而是直接去看源代码,在此期间收获了不少,
  发现内核写的确实很好很强大,要一下搞定它,那是痴心妄想,
  所以我从一些小的地方开始,在遇到内核源码时就去读它。
  今天读到一个有用的函数:copy_to_user(),其在内核定义如下:

835

 836 * copy_to_user: - Copy a block of data into user space.
 837 * @to: Destination address, in user space.
 838 * @from: Source address, in kernel space.
 839 * @n: Number of bytes to copy.
 840 *
 841 * Context: User context only. This function may sleep.
 842 *
 843 * Copy data from kernel space to user space.
 844 *
 845 * Returns number of bytes that could not be copied.
 846 * On success, this will be zero.
 847
 
 848 unsigned long
 849 copy_to_user(void __user *to, const void *from, unsigned long n)
 850 {
 851 if (access_ok(VERIFY_WRITE, to, n))
 852 n = __copy_to_user(to, from, n);
 853 return n;
 854 }
 

   相信稍微懂英语的人都能读懂前面的注释,
    所复制的内容是从from来,到to去,复制n个位。
 两个函数:access_ok()和__copy_to_user(),

85/**
  86 * access_ok: - Checks if a user space pointer is valid
  87 * @type: Type of access: %VERIFY_READ or %VERIFY_WRITE. Note that
  88 * %VERIFY_WRITE is a superset of %VERIFY_READ - if it is safe
  89 * to write to a block, it is always safe to read from it.
  90 * @addr: User space pointer to start of block to check
  91 * @size: Size of block to check
  92 *
  93 * Context: User context only. This function may sleep.
  94 *
  95 * Checks if a pointer to a block of memory in user space is valid.
  96 *
  97 * Returns true (nonzero) if the memory block may be valid, false (zero)
  98 * if it is definitely invalid.
  99 *
 100 * Note that, depending on architecture, this function probably just
 101 * checks that the pointer is in the user space range - after calling
 102 * this function, memory access functions may still return -EFAULT.
 
 
 
 104#ifdef CONFIG_MMU
 105#define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0))
 106#else
 107static inline int access_ok(int type, const void *addr, unsigned long size)
 108{
 109 extern unsigned long memory_start, memory_end;
 110 unsigned long val = (unsigned long)addr;
 111
 112 return ((val >= memory_start) && ((val + size) < memory_end));
 113}
 114#endif /* CONFIG_MMU */
 
/*

 其功能是检查用户空间是否合法,它的第一个参数:type,有两种类型? 篤ERIFY_READVERIFY_WRITE,前者为可读,后者可写,
 注意:如果标志为可写(VERIFY_WRITE)时,必然可读?
  VERIFY_WRITE is a superset of %VERIFY_READ)。
检查过程如下:addr为起始地址,size为所要复制的大小,
那么从addr到addr+size则是所要检查的空间,
如果它的范围在memory_start和memory_end之间的话,则返回真?V劣趍emory_start详细信息,我没有读。
 到此为止,如果检查合法,那么OK,我们来实现真正的复制功能:
 __copy_to_user

80static inline __kernel_size_t __copy_to_user(void __user *to, const void *from,
  81 __kernel_size_t n)
  82{
  83 return __copy_user((void __force *)to, from, n);
  84}
 

    哈哈,又遇到一个函数:__copy_user(),这个函数才真正在做底层的复制工作。
  想继续深入的:看这里

 补充:    
    __copy_to_user()就是对__copy_user()的封装.其实, 在函数:copy_from_user()中,也调用了这个函数。也就是说这个函数实现了内核空间与用户空间的相互复制。

 

*/
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值