歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> LINUX字符設備驅動程序實例(scull)

LINUX字符設備驅動程序實例(scull)

日期:2017/3/3 12:16:55   编辑:Linux技術
該驅動程序在UBUNTU10.04LTS編譯通過,系統內核為linux-2.6.32-24(可使用uname -r 命令來查看當前內核的版本號)
由於安裝UBUNTU10.04LTS時,沒有安裝LINUX內核源碼,因此需要在www.kernel.org下載LINUX源碼,下載linux-2.6.32.22.tar.bz2(與系統運行的LINUX內核版本盡量保持一致),使用如下命令安裝內核:
1.解壓內核
cd /us/src

tar jxvf linux-2.6.32.22.tar.bz2

2.為系統的include創建鏈接文件
cd /usr/include

rm -rf asm linux scsi

ln -s /usr/src/linux-2.6.32.22/include/asm-generic asm

ln -s /usr/src/linux-2.6.32.22/include/linux linux

ln -s /usr/src/linux-2.6.32.22/include/scsi scsi

LINUX內核源碼安裝完畢
【2.驅動程序代碼】
/******************************************************************************

*Name: memdev.c

*Desc: 字符設備驅動程序的框架結構,該字符設備並不是一個真實的物理設備,

* 而是使用內存來模擬一個字符設備

*Parameter: 

*Return:

*Author: yoyoba([email protected])

*Date: 2010-9-26

*Modify: 2010-9-26

********************************************************************************/

#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>

#include "memdev.h"

static mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp; /*設備結構體指針*/

struct cdev cdev; 

/*文件打開函數*/

int mem_open(struct inode *inode, struct file *filp)

{

    struct mem_dev *dev;

    

    /*獲取次設備號*/

    int num = MINOR(inode->i_rdev);

    if (num >= MEMDEV_NR_DEVS) 

            return -ENODEV;

    dev = &mem_devp[num];

    

    /*將設備描述結構指針賦值給文件私有數據指針*/

    filp->private_data = dev;

    

    return 0; 

}

/*文件釋放函數*/

int mem_release(struct inode *inode, struct file *filp)

{

  return 0;

}

/*讀函數*/

static ssize_t mem_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 mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/

  /*判斷讀位置是否有效*/

  if (p >= MEMDEV_SIZE)

    return 0;

  if (count > MEMDEV_SIZE - p)

    count = MEMDEV_SIZE - p;

  /*讀數據到用戶空間*/

  if (copy_to_user(buf, (void*)(dev->data + 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 mem_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 mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/

  

  /*分析和獲取有效的寫長度*/

  if (p >= MEMDEV_SIZE)

    return 0;

  if (count > MEMDEV_SIZE - p)

    count = MEMDEV_SIZE - p;

    

  /*從用戶空間寫入數據*/

  if (copy_from_user(dev->data + 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 mem_llseek(struct file *filp, loff_t
 offset, int whence)

{ 

    loff_t newpos;

    switch(whence) {

      case 0: /* SEEK_SET */

        newpos = offset;

        break;

      case 1: /* SEEK_CUR */

        newpos = filp->f_pos + offset;

        break;

      case 2: /* SEEK_END */

        newpos = MEMDEV_SIZE -1 + offset;

        break;

      default: /* can't happen */

        return -EINVAL;

    }

    if ((newpos<0) || (newpos>MEMDEV_SIZE))

     return -EINVAL;

     

    filp->f_pos = newpos;

    return newpos;

}

/*文件操作結構體*/

static const struct file_operations mem_fops =

{

  .owner = THIS_MODULE,

  .llseek = mem_llseek,

  .read = mem_read,

  .write = mem_write,

  .open = mem_open,

  .release = mem_release,

};

/*設備驅動模塊加載函數*/

static int memdev_init(void)

{

  int result;

  int i;

  dev_t devno = MKDEV(mem_major, 0);

  /* 靜態申請設備號*/

  if (mem_major)

    result = register_chrdev_region(devno, 2, "memdev");

  else /* 動態分配設備號 */

  {

    result = alloc_chrdev_region(&devno, 0, 2, "memdev");

    mem_major = MAJOR(devno);

  } 

  

  if (result < 0)

    return result;

  /*初始化cdev結構*/

  cdev_init(&cdev, &mem_fops);

  cdev.owner = THIS_MODULE;

  cdev.ops = &mem_fops;

  

  /* 注冊字符設備 */

  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

   

  /* 為設備描述結構分配內存*/

  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);

  if (!mem_devp) /*申請失敗*/

  {

    result = - ENOMEM;

    goto fail_malloc;

  }

  memset(mem_devp, 0, sizeof(struct mem_dev));

  

  /*為設備分配內存*/

  for (i=0; i < MEMDEV_NR_DEVS; i++) 

  {

        mem_devp[i].size = MEMDEV_SIZE;

        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);

        memset(mem_devp[i].data, 0, MEMDEV_SIZE);

  }

    

  return 0;

  fail_malloc: 

  unregister_chrdev_region(devno, 1);

  

  return result;

}

/*模塊卸載函數*/

static void memdev_exit(void)

{

  cdev_del(&cdev); /*注銷設備*/

  kfree(mem_devp); /*釋放設備結構體內存*/

  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/

}

MODULE_AUTHOR("David Xie");

MODULE_LICENSE("GPL");

module_init(memdev_init);

module_exit(memdev_exit);

/************************

*memdev.h

************************/

#ifndef _MEMDEV_H_

#define _MEMDEV_H_

#ifndef MEMDEV_MAJOR

#define MEMDEV_MAJOR 260 /*預設的mem的主設備號*/

#endif

#ifndef MEMDEV_NR_DEVS

#define MEMDEV_NR_DEVS 2 /*設備數*/

#endif

#ifndef MEMDEV_SIZE

#define MEMDEV_SIZE 4096

#endif

/*mem設備描述結構體*/

struct mem_dev 

{ 

  char *data; 

  unsigned long size; 

};

#endif /* _MEMDEV_H_ */

【3.編譯驅動程序模塊】
Makefile文件的內容如下:
ifneq ($(KERNELRELEASE),)

obj-m:=memdev.o

else

KERNELDIR:=/lib/modules/$(shell uname -r)/build

PWD:=$(shell pwd)

default:

 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

 rm -rf *.o *.mod.c *.mod.o *.ko

endif

切換到root下,執行make時,如果UBUNTU是使用虛擬機安裝的,那麼執行make時,不要在ubuntu和windows的共享目錄下,否則會出錯。
root@VMUBUNTU:~# make

make -C /lib/modules/2.6.32-24-generic/build M=/root modules

make[1]: Entering directory `/usr/src/linux-headers-2.6.32-24-generic'

  CC [M] /root/memdev.o

/root/memdev.c:15: warning: type defaults to ‘int’ in declaration of ‘mem_major’

/root/memdev.c: In function ‘mem_read’:

/root/memdev.c:71: warning: format ‘%d’ expects type ‘int’, but argument 3 has type ‘long unsigned int’

/root/memdev.c: In function ‘mem_write’:

/root/memdev.c:99: warning: format ‘%d’ expects type ‘int’, but argument 3 has type ‘long unsigned int’

  Building modules, stage 2.

  MODPOST 1 modules

  CC /root/memdev.mod.o

  LD [M] /root/memdev.ko

make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-24-generic'

ls查看當前目錄的內容
root@VMUBUNTU:~# ls

Makefile memdev.h memdev.mod.c memdev.o Module.symvers

memdev.c memdev.ko memdev.mod.o modules.order

這裡的memdev.ko就是生成的驅動程序模塊。
通過insmod命令把該模塊插入到內核
root@VMUBUNTU:~# insmod memdev.ko

查看插入的memdev.ko驅動
root@VMUBUNTU:~# cat /proc/devices

Character devices:

  1 mem

  4 /dev/vc/0

  4 tty

  4 ttyS

  5 /dev/tty

  5 /dev/console

  5 /dev/ptmx

260 memdev

  6 lp

  7 vcs

 10 misc

 13 input

 14 sound

 21 sg

 29 fb

 99 ppdev

108 ppp

116 alsa

128 ptm

136 pts

180 usb

189 usb_device

226 drm

251 hidraw

252 usbmon

253 bsg

254 rtc

Block devices:

  1 ramdisk

259 blkext

  7 loop

  8 sd

  9 md

 11 sr

 65 sd

 66 sd

 67 sd

 68 sd

 69 sd

 70 sd

 71 sd

128 sd

129 sd

130 sd

131 sd

132 sd

133 sd

134 sd

135 sd

252 device-mapper

253 pktcdvd

254 mdp

可以看到memdev驅動程序被正確的插入到內核當中,主設備號為260,該設備號為memdev.h中定義的#define MEMDEV_MAJOR 260。
如果這裡定義的主設備號與系統正在使用的主設備號沖突,比如主設備號定義如下:#define MEMDEV_MAJOR 254,那麼在執行insmod命令時,就會出現如下的錯誤:
root@VMUBUNTU:~# insmod memdev.ko

insmod: error inserting 'memdev.ko': -1 Device or resource busy

查看當前設備使用的主設備號
root@VMUBUNTU:~# cat /proc/devices

Character devices:

  1 mem

  4 /dev/vc/0

  4 tty

  4 ttyS

  5 /dev/tty

  5 /dev/console

  5 /dev/ptmx

  6 lp

  7 vcs

 10 misc

 13 input

 14 sound

 21 sg

 29 fb

 99 ppdev

108 ppp

116 alsa

128 ptm

136 pts

180 usb

189 usb_device

226 drm

251 hidraw

252 usbmon

253 bsg

254 rtc

Block devices:

  1 ramdisk

259 blkext

  7 loop

  8 sd

  9 md

 11 sr

 65 sd

 66 sd

 67 sd

 68 sd

 69 sd

 70 sd

 71 sd

128 sd

129 sd

130 sd

131 sd

132 sd

133 sd

134 sd

135 sd

252 device-mapper

253 pktcdvd

254 mdp

發現字符設備的254主設備號為rtc所使用,因此會出現上述錯誤,解決方法只需要在memdev.h中修改主設備號的定義即可。
【4.編寫應用程序,測試該驅動程序】
首先應該在/dev/目錄下創建與該驅動程序相對應的文件節點,使用如下命令創建:
root@VMUBUNTU:/dev# mknod memdev c 260 0

使用ls查看創建好的驅動程序節點文件
root@VMUBUNTU:/dev# ls -al memdev

crw-r--r-- 1 root root 260, 0 2010-09-26 17:28 memdev

編寫如下應用程序,來對驅動程序進行測試。
/******************************************************************************

*Name: memdevapp.c

*Desc: memdev字符設備驅動程序的測試程序。先往memedev設備寫入內容,然後再

* 從該設備中把內容讀出來。

*Parameter: 

*Return:

*Author: yoyoba([email protected])

*Date: 2010-9-26

*Modify: 2010-9-26

********************************************************************************/

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

#include <unistd.h>

#include <linux/i2c.h>

#include <linux/fcntl.h>

int main()

{

 int fd;

 char buf[]="this
 is a example for character devices driver by yoyoba!";//寫入memdev設備的內容

 char buf_read[4096]; //memdev設備的內容讀入到該buf中

 

 if((fd=open("/dev/memdev",O_RDWR))==-1) //打開memdev設備

  printf("open memdev WRONG!\n");

 else

  printf("open memdev SUCCESS!\n");

  

 printf("buf is %s\n",buf); 

 write(fd,buf,sizeof(buf)); //把buf中的內容寫入memdev設備

 

 lseek(fd,0,SEEK_SET); //把文件指針重新定位到文件開始的位置

 

 read(fd,buf_read,sizeof(buf)); //把memdev設備中的內容讀入到buf_read中

 

 printf("buf_read is %s\n",buf_read);

 

 return 0;

}

編譯並執行該程序
root@VMUBUNTU:/mnt/xlshare# gcc -o mem memdevapp.c

root@VMUBUNTU:/mnt/xlshare# ./mem

open memdev SUCCESS!

buf is this is a example for character devices driver by yoyoba!

buf_read is this is a example for character devices driver by yoyoba!

表明驅動程序工作正常。。。
【5.LINUX是如何make驅動程序模塊的】
Linux內核是一種單體內核,但是通過動態加載模塊的方式,使它的開發非常靈活 方便。那麼,它是如何編譯內核的呢?我們可以通過分析它的Makefile入手。以下是 一個簡單的hello內核模塊的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
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
endif
當我們寫完一個hello模塊,只要使用以上的makefile。然後make一下就行。 假設我們把hello模塊的源代碼放在/home/study/prog/mod/hello/下。 當我們在這個目錄運行make時,make是怎麼執行的呢? LDD3第二章第四節“編譯和裝載”中只是簡略地說到該Makefile被執行了兩次, 但是具體過程是如何的呢?
首先,由於make 後面沒有目標,所以make會在Makefile中的第一個不是以.開頭 的目標作為默認的目標執行。於是default成為make的目標。make會執行 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules shell是make內部的函數,假設當前內核版本是2.6.13-study,所以$(shell uname -r)的結果是 2.6.13-study 這裡,實際運行的是
make -C /lib/modules/2.6.13-study/build M=/home/study/prog/mod/hello/ modules
/lib/modules/2.6.13-study/build是一個指向內核源代碼/usr/src/linux的符號鏈接。 可見,make執行了兩次。第一次執行時是讀hello模塊的源代碼所在目錄/home/s tudy/prog/mod/hello/下的Makefile。第二次執行時是執行/usr/src/linux/下的Makefile時.
但是還是有不少令人困惑的問題: 1.這個KERNELRELEASE也很令人困惑,它是什麼呢?在/home/study/prog/mod/he llo/Makefile中是沒有定義這個變量的,所以起作用的是else…endif這一段。不 過,如果把hello模塊移動到內核源代碼中。例如放到/usr/src/linux/driver/中, KERNELRELEASE就有定義了。 在/usr/src/linux/Makefile中有 162 KERNELRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)$(LOCALVERSION)
這時候,hello模塊也不再是單獨用make編譯,而是在內核中用make modules進行 編譯。 用這種方式,該Makefile在單獨編譯和作為內核一部分編譯時都能正常工作。
2.這個obj-m := hello.o什麼時候會執行到呢? 在執行:
make -C /lib/modules/2.6.13-study/build M=/home/study/prog/mod/hello/ modules
時,make 去/usr/src/linux/Makefile中尋找目標modules: 862 .PHONY: modules 863 modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) 864 @echo ' Building modules, stage 2.'; 865 $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost
可以看出,分兩個stage: 1.編譯出hello.o文件。 2.生成hello.mod.o hello.ko 在這過程中,會調用 make -f scripts/Makefile.build obj=/home/study/prog/mod/hello 而在 scripts/Makefile.build會包含很多文件: 011 -include .config 012 013 include $(if $(wildcard $(obj)/Kbuild), $(obj)/Kbuild, $(obj)/Makefile)
其中就有/home/study/prog/mod/hello/Makefile 這時 KERNELRELEASE已經存在。 所以執行的是: obj-m:=hello.o
關於make modules的更詳細的過程可以在scripts/Makefile.modpost文件的注釋 中找到。如果想查看make的整個執行過程,可以運行make -n。
Copyright © Linux教程網 All Rights Reserved