一、字符设备基础

字符设备:是指只能一个字节一个字节进行读写操作的设备,不能随机读取设备中的某一数据、读取数据要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。

一般每个字符设备或者块设备都会在/dev目录(可以是任意目录,这样是为了统一)下对应一个设备文件。linux用户层程序通过设备文件来使用驱动程序操作字符设备或块设备

二、驱动设备基础

驱动程序

驱动程序的作用是应用程序与硬件之间的一个中间软件层,驱动程序应该为应用程序展现硬件的所有功能,不应该强加其他的约束,对于硬件使用的权限和限制应该由应用程序层控制

Linux 的驱动开发调试有两种方法,一种是直接编译到内核,再运行新的内核来测试;二是编译为模块的形式,单独加载运行调试。第一种方法效率较低,但在某些场合是唯一的方法。模块方式调试效率很高,它使用 insmod 工具将编译的模块直接插入内核,如果出现故障,可以使用rmmod从内核中卸载模块,不需要重新启动内核,这使驱动调试效率大大提高


Linux内核简介

Linux内核是一个整体是结构.因此向内核添加任何东西.或者删除某些功能,都十分困难。为了解决这个问题,引入了内核机制,从而可以可以动态的想内核中添加或者删除模块

Linux的内核模块机制允许开发者动态的向内核添加功能,我们常见的文件系统、驱动程序等都可以通过模块的方式添加到内核而无需对内核重新编译,这在很大程度上减少了操作的复杂度。模块机制使内核预编译时不必包含很多无关功能,把内核做到最精简,后期可以根据需要进行添加。而针对驱动程序,因为涉及到具体的硬件,很难使通用的,且其中可能包含了各个厂商的私密接口,厂商几乎不会允许开发者把源代码公开,这就和Linux的许可相悖,模块机制很好的解决了这个冲突,允许驱动程序后期进行添加而不合并到内核


驱动程序与应用程序的区别

应用程序一般有一个main函数,从头到尾执行一个任务。驱动程序却不同,它没有 main 函数,通过使用宏module_init(初始化函数名),将初始化函数加入内核全局初始化函数列表中,在内核初始化时执行驱动的初始化函数,从而完成驱动的初始化和注册,之后驱动便停止等待被应用软件调用。驱动程序中有一个宏 moudule_exit(退出处理函数名)注册退出处理函数。它在驱动退出时被调用

应用程序可以和 GLIBC 库连接,因此可以包含标准的头文件,比如 ,在驱动程序中是不能使用标准C库的,因此不能调用所有的C库函数,比如输出打印函数只能使用内核的 printk函数,包含的头文件只能是内核的头文件


设备的分类

以 Linux 的方式看待设备可区分为3种基本设备类型: 字符设备、块设备、网络设备


字符设备: 一个字符(char)设备是一种可以当作一个字节流来存取的设备(如同一个文件); 一个字符驱动负责实现这种行为。这样的驱动常常至少实现open, close, read, 和 write 系统调用. 文本控制台(/dev/console)和串口(/dev/ttyS0及其它)是字符设备的例子, 因为它们很好地展现了流的抽象。字符设备通过文件系统结点来存取, 例如/dev/tty1和/dev/lp0。在一个字符设备和一个普通文件之间唯一有关的不同就是,你经常可以在普通文件中移来移去, 但是大部分字符设备仅仅是数据通道, 你只能顺序存取


块设备: 如同字符设备, 块设备通过位于 /dev 目录的文件系统结点来存取。 一个块设备(例如一个磁盘)应该是可以驻有一个文件系统的。在大部分的 Unix 系统, 一个块设备只能处理这样的 I/O 操作,传送一个或多个长度经常是 512 字节( 或一个更大的2的幂的数 )的整块。Linux则相反, 允许应用程序读写一个块设备象一个字符设备一样---它允许一次传送任意数目的字节。结果就是,块和字符设备的区别仅仅在内核在内部管理数据的方式上, 并且因此在内核/驱动的软件接口上不同。如同一个字符设备, 每个块设备都通过一个文件系统结点被存取的, 它们之间的区别对用户是透明的。块驱动和字符驱动相比, 与内核的接口完全不同


网络接口: 任何网络事务都通过一个接口来进行,就是说,一个能够与其他主机交换数据的设备。通常,一个接口是一个硬件设备,但是它也可能是一个纯粹的软件设备,比如环回接口。一个网络接口负责发送和接收数据报文,在内核网络子系统的驱动下,不必知道单个事务是如何映射到实际的被发送的报文上的。很多网络连接(特别那些使用TCP的)是面向流的, 但是网络设备却常常设计成处理报文的发送和接收。一个网络驱动对单个连接一无所知,它只处理报文


Linux下的大部分驱动,是以模块形式进行编写的。这些驱动程序源码可以修改到内核中,也可以将它们编译成模块形式,在需要的时候动态加载


字符设备是3大类设备中较简单的一类设备,其驱动程序中完成的主要工作是初始化、添加和删除struct cdev 结构体,申请和释放设备号,以及填充 struct file_operations结构体中断的操作函数,实现 struct file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作


驱动程序实现框架


字符设备驱动实验_嵌入式

主要功能函数列表


字符设备驱动实验_#include_02

三、试验步骤

1.linux源码及相关软件下载

在进行字符驱动设备编写之前,需要先下载linux内核源码,并且下载相应软件以支持对linux源码的操作

下载linux-3.0.15,并将文件解压到指定目录


在试验之前,请确保你的内核源码包起码编译过一次,因为编译驱动时候需要用到内核源码的相关头文件及编译内核时所生成的过程文件,否则即便内核目录指定正确,也会报错。


进入内核目录(必须是内核目录)之后,终端输入:

make -j8(创建8个进程对内核进行编译,提高编译效率)


字符设备驱动实验_linux_03

进入解压生成的目录,终端输入:make menocofig对内核源码进行编辑

字符设备驱动实验_字符驱动_04

执行结果如下图所示

字符设备驱动实验_字符驱动_05


  1. 创建试验目录:/nfsroot/kernel/wang

终端输入:mkdir /nfsroot/kernel/wang创建试验目录

使用vim编写源程序以及相关文件,详细内容见附件

字符设备驱动实验_字符驱动_06

注意:

Makefile中的: #KERNELDIR = #CROSS_COMPILE=  两行宏变量定义用于指定编译器和内核目录,其中内核目录一定要与用户自己内核路径一致,这里使用的是出厂默 认宿主机安装路径/UP-CUP4412/SRC/kernel/linux-3.0.15。编译器使用arm-none-linux-gnueabi-gcc并 且保证你的内核源码包起码编译过一次,因为编译驱动时候需要用到内核源码的相关头文件及编译内核时所生成的 过程文件,否则即便内核目录指定正确,也会报错

3.进入试验目录,编译源程序

使用make命令编译文件

当前目录下生成驱动程序 demo.ko 和应用测试程序 test_demo

字符设备驱动实验_linux_07

4.nfs挂载试验目录

启动移动互联网教研平台,连接好网线和串口线,使用X-shell连接宿主机和开发板之后,在开发板的终端输入:ifconfig eth0 192.168.1.199(根据个人情况)

字符设备驱动实验_嵌入式_08

字符设备驱动实验_#include_09


终端输入:mountnfs 192.168.1.112:/nfsroot /mnt/nfs

字符设备驱动实验_嵌入式_10

将宿主机的/nfsroot目录挂载到开发板的/mnt/nfs目录,之后进入/mnt/nfs查看,发现成功挂载

字符设备驱动实验_嵌入式_10

  1. 手动加载驱动程序

进入开发板终端,输入insmod demo.ko

字符设备驱动实验_嵌入式_10

6、通过设备号建立驱动设备节点

使用 cat /proc/devices 查看设备号

字符设备驱动实验_#include_13

查看找demo.ko驱动设备号,这里显示为 250,在 UP-MobNet-II 型网关部分系统/dev 目录下手动建立设备节点demo

终端输入:mknod /dev/demo c 250 0

字符设备驱动实验_字符设备_14

c:表示 char 型设备 250: 表示主设备号 0:表示次设备号


7查看下自己建立的设备 demo 属性

终端输入:ll /dev/demo

字符设备驱动实验_字符设备_14

执行测试程序test_demo测试驱动及设备,执行结果如下:


字符设备驱动实验_字符设备_16

试验成功!!!


demo.c源码如下


#ifdef MODULE
#include <linux/module.h>

#ifdef CONFIG_DEVFS_FS
#include <linux/devfs_fs_kernel.h>
#endif


#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/poll.h> /* COPY_TO_USER */
#include <linux/uaccess.h>
#include <asm/system.h> /* cli(), *_flags */

#define DEVICE_NAME "UP-TECH DEMO"
#define DEMORAW_MINOR 1
#define DEMO_Devfs_path "demo/0"

static int demoMajor = 0;
static int MAX_BUF_LEN=1024;
static char drv_buf[1024];
static int length=0;

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

static void do_write(void)
{

int i;
int len = length;
int mid = len>>1;
char tmp;
for(i = 0; i < mid; i++,len--){
tmp = drv_buf[len-1];
drv_buf[len-1] = drv_buf[i];
drv_buf[i] = tmp;
}
}
/***********************************************************************************/
static ssize_t demo_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos)
{
if(count > MAX_BUF_LEN)
length = MAX_BUF_LEN;
length = copy_from_user(drv_buf , buffer, count);
do_write();

return length;
}
/***********************************************************************************/
static ssize_t demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
{
if(count > MAX_BUF_LEN)
length = MAX_BUF_LEN;
length = copy_to_user(buffer, drv_buf,count);

return length;
}
/***********************************************************************************/
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd){
case 1:printk("runing command 1 \n");break;
case 2:printk("runing command 2 \n");break;
default:
printk("error cmd number\n");break;
}
return 0;
}
/***********************************************************************************/
static int demo_open(struct inode *inode, struct file *filp)
{
printk(KERN_DEBUG" device open sucess!\n");
return 0;
}
/***********************************************************************************/
static int demo_release(struct inode *inode, struct file *filp)
{
// MOD_DEC_USE_COUNT;
printk(KERN_DEBUG "device release\n");
return 0;
}

/***********************************************************************************/
static struct file_operations pxa270_fops = {
owner: THIS_MODULE,
write: demo_write,
read: demo_read,
unlocked_ioctl: demo_ioctl,
open: demo_open,
release: demo_release,
};
/***********************************************************************************/

static int __init demo_init(void)
{
int ret;
ret = register_chrdev(0, DEVICE_NAME, &pxa270_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't get major number\n");
return ret;
}
demoMajor=ret;
printk("demo driver initialized \n");
#ifdef CONFIG_DEVFS_FS
devfs_mk_cdev(MKDEV(demoMajor, DEMORAW_MINOR),
S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, DEMO_Devfs_path);
#endif
return 0;
}

/***********************************************************************************/
#ifdef MODULE
void __exit demo_exit(void)
{
#ifdef CONFIG_DEVFS_FS
devfs_remove(DEMO_Devfs_path);
#endif
unregister_chrdev(demoMajor, DEVICE_NAME);
}
module_exit(demo_exit);
#endif

/***********************************************************************************/
module_init(demo_init);

MODULE_LICENSE("Dual BSD/GPL");
#endif // MODULE



test_demo.c源码如下


#ifdef MODULE
#include <linux/module.h>

#ifdef CONFIG_DEVFS_FS
#include <linux/devfs_fs_kernel.h>
#endif


#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/poll.h> /* COPY_TO_USER */
#include <linux/uaccess.h>
#include <asm/system.h> /* cli(), *_flags */

#define DEVICE_NAME "UP-TECH DEMO"
#define DEMORAW_MINOR 1
#define DEMO_Devfs_path "demo/0"

static int demoMajor = 0;
static int MAX_BUF_LEN=1024;
static char drv_buf[1024];
static int length=0;

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

static void do_write(void)
{

int i;
int len = length;
int mid = len>>1;
char tmp;
for(i = 0; i < mid; i++,len--){
tmp = drv_buf[len-1];
drv_buf[len-1] = drv_buf[i];
drv_buf[i] = tmp;
}
}
/***********************************************************************************/
static ssize_t demo_write(struct file *file, const char __user *buffer, size_t count, loff_t * ppos)
{
if(count > MAX_BUF_LEN)
length = MAX_BUF_LEN;
length = copy_from_user(drv_buf , buffer, count);
do_write();

return length;
}
/***********************************************************************************/
static ssize_t demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
{
if(count > MAX_BUF_LEN)
length = MAX_BUF_LEN;
length = copy_to_user(buffer, drv_buf,count);

return length;
}
/***********************************************************************************/
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd){
case 1:printk("runing command 1 \n");break;
case 2:printk("runing command 2 \n");break;
default:
printk("error cmd number\n");break;
}
return 0;
}
/***********************************************************************************/
static int demo_open(struct inode *inode, struct file *filp)
{
printk(KERN_DEBUG" device open sucess!\n");
return 0;
}
/***********************************************************************************/
static int demo_release(struct inode *inode, struct file *filp)
{
// MOD_DEC_USE_COUNT;
printk(KERN_DEBUG "device release\n");
return 0;
}

/***********************************************************************************/
static struct file_operations pxa270_fops = {
owner: THIS_MODULE,
write: demo_write,
read: demo_read,
unlocked_ioctl: demo_ioctl,
open: demo_open,
release: demo_release,
};
/***********************************************************************************/

static int __init demo_init(void)
{
int ret;
ret = register_chrdev(0, DEVICE_NAME, &pxa270_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't get major number\n");
return ret;
}
demoMajor=ret;
printk("demo driver initialized \n");
#ifdef CONFIG_DEVFS_FS
devfs_mk_cdev(MKDEV(demoMajor, DEMORAW_MINOR),
S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, DEMO_Devfs_path);
#endif
return 0;
}

/***********************************************************************************/
#ifdef MODULE
void __exit demo_exit(void)
{
#ifdef CONFIG_DEVFS_FS
devfs_remove(DEMO_Devfs_path);
#endif
unregister_chrdev(demoMajor, DEVICE_NAME);
}
module_exit(demo_exit);
#endif

/***********************************************************************************/
module_init(demo_init);

MODULE_LICENSE("Dual BSD/GPL");
#endif // MODULE
root@ubuntu:/nfsroot/kernel/wang# cat test_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>


void showbuf(char *buf);
int MAX_LEN=32;

int main()
{
int fd;
int i;
char buf[255];

for(i=0; i<MAX_LEN; i++){
buf[i]=i;
}

fd=open("/dev/demo",O_RDWR);
if(fd < 0){
printf("####DEMO device open fail####\n");
return (-1);
}
printf("write %d bytes data to /dev/demo \n",MAX_LEN);
showbuf(buf);
write(fd,buf,MAX_LEN);

printf("Read %d bytes data from /dev/demo \n",MAX_LEN);
read(fd,buf,MAX_LEN);
showbuf(buf);

close(fd);
return 0;

}


void showbuf(char *buf)
{
int i,j=0;
for(i=0;i<MAX_LEN;i++){
if(i%4 ==0)
printf("\n%4d: ",j++);
printf("%4d ",buf[i]);
}
printf("\n*****************************************************\n");
}




modules.order内容如下

kernel//nfsroot/kernel/wang/demo.ko


Makefile内容如下

# To build modules outside of the kernel tree, we run "make"
# in the kernel source tree; the Makefile these then includes this
# Makefile once again.
# This conditional selects whether we are being included from the
# kernel Makefile or not.

TARGET = test_demo
CROSS_COMPILE = arm-none-linux-gnueabi-

CC = $(CROSS_COMPILE)gcc
STRIP = $(CROSS_COMPILE)strip
#CFLAGS = -O2


ifeq ($(KERNELRELEASE),)

# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
# KERNELDIR ?=/home/sprife/kernel/linux-2.6.24.4
KERNELDIR ?=/nfsroot/kernel/linux-3.0.15
# KERNELDIR ?= /usr/src/kernels/2.6.9-42.EL-smp-i686/
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)

all: $(TARGET) modules

$(TARGET):
$(CC) -o $(TARGET) $(TARGET).c

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

modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions $(TARGET)

.PHONY: modules modules_install clean

else
# called from kernel build system: just declare what our modules are
obj-m := demo.o

endif


Module.symvers为空文件