1.驱动在Linux中有着比较重要地位,Linux中一切皆文件的思想,将所有的设备(而不仅是磁盘上的文件)全部看成文件,纳入文件系统的范畴。
每一项都至少由文件系统中的一个文件(更确切地说是节点)代表,因而都有一个“文件名”。每个这样的设备文件都惟一地确定了系统中的一项设备。应用程序通过设备的文件名寻访具体的设备,而设备则像普通文件一样受到文件系统访问权限控制机制的保护。
应用程序通常可以通过系统调用open()“打开”设备文件,建立起与目标设备的连接。代表着该设备的文件节点中记载着建立这种连接所需的信息,对于执行该应用程序的进程而言,建立起的连接就表现为一个已打开文件。
打开了代表目标设备的文件,即建立起与设备的连接以后,就可以通过read()、write()、ioctl()等常规文件操作对目标设备进行操作。从应用程序的角度看。设备文件逻辑上的空间是个线性空间。从这个逻辑空间到具体设备的物理空间的映射则由内核提供,并划分成文件操作与设备驱动两个层次。
这样,对于一个具体的设备来说,文件操作和设备驱动就成为同一事物的不同层次,而不是互相独立或平行的两个概念。
2.Linux将设备分成两大类。一类是块设备,一类是字符设备。
块设备:以磁盘那样以记录块或“扇区”为单位,成块进行输入/输出的设备。
字符设备:像键盘那样以字符(字节)为单位,逐个进行输入/输出的设备,称为“字符设备”。
3.驱动的连接
使用系统调用mknod(),在文件系统中创建一个代表此项设备的文件节点,使设备在系统中成为可见,使应用程序可以访问。
设备驱动程序的连接
设备驱动程序连接到系统有静态连接和动态连接两种方式。
静态连接:将驱动程序和系统一起编译生成。
动态连接:用户挑选一些事先已经编译好的模块,同时由系统生成工具对以高级语言编写的“设备表”加以修改,在表中加入相应驱动程序的函数指针,然后加以编译并将所挑选的模块与内核连接在一起。
/sbin/insmod程序,不但负责模块与内核的连接,也负责把模块的目标文件(.o文件)“装入到内核空间”。与/sbin/insmod相对应,还有一个应用程序/sbin/rmmod,其作用是将一个已安装的模块从内核中拆除。当然,像insmod一样,只能特权用户才能执行rmmod。
4.驱动实验
编写hello.c的模块代码
/* hello.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy_to_user() */
#include <linux/slab.h>
#include <linux/uaccess.h>
/* function prototypes */
static int hello_open( struct inode *inode, struct file *filp );
static int hello_release( struct inode *inode, struct file *filp );
ssize_t hello_read( struct file *flip, char __user *buf, size_t count,loff_t
*f_pos);
ssize_t hello_write( struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos );
static int hello_major = 0; /* major device number */
//模块的作者
MODULE_AUTHOR( "xiaobenzhu" );
//模块的版权 代码遵从两种协议BSD 和 GPL
MODULE_LICENSE( "Dual BSD/GPL" );
static struct cdev helloDev; /* hello device structure */
/* file operations for hello device */
static struct file_operations hello_ops = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_read,
.write = hello_write,
.release = hello_release,
};
/* Open the device */
static int hello_open( struct inode *inode, struct file *filp ){
printk( KERN_NOTICE"Hello device open!\n" );
return 0;
}
/* Close hello_device */
static int hello_release( struct inode *inode, struct file *filp ){
printk( KERN_NOTICE"Hello device close!\n" );
return 0;
}
/* set up the cdev stucture for a device */
static void hello_setup_cdev( struct cdev *dev, int minor, struct
file_operations *fops ){
int err;
int devno = MKDEV( hello_major, minor );
/* initialize the cdev struct */
cdev_init( dev,fops );
dev->owner = THIS_MODULE;
dev->ops = fops; /* why not do it in cdev_init ? */
err = cdev_add( dev, devno, 1 ); /* register the cdev in the kernel */
if( err )
printk( KERN_NOTICE"Error %d adding hello%d\n",err ,minor );
}
/* Module housekeeping */
static int hello_init(void){
int result;
dev_t dev = MKDEV( hello_major, 0 );
/* alloc the major device number dynamicly */
result = alloc_chrdev_region(&dev, 0 ,1, "hello" );
if( result < 0 ){
printk( KERN_NOTICE"Hello: unable to get major %d\n",hello_major );
return result;
}
hello_major = MAJOR(dev);
/* set up devices, in this case, there is only one device */
printk( KERN_NOTICE"hello init: %d, %d\n",hello_major,0 );
//printk( KERN_ALERT"hello init: %d, %d\n",hello_major,0 );
hello_setup_cdev(&helloDev, 0 , &hello_ops );
return 0;
}
/* Exit routine */
static void hello_exit(void){
/* remove the cdev from kernel */
cdev_del(&helloDev );
/* release the device numble alloced earlier */
unregister_chrdev_region( MKDEV( hello_major, 0 ), 1 );
printk( KERN_NOTICE"hello exit. major:%d,minor %d\n",hello_major,0 );
}
/* user read from hello device*/
ssize_t hello_read( struct file *flip, char __user *buf, size_t count,loff_t
*f_pos){
ssize_t retval = 0;
char *bank;
bank = kmalloc(count+1, GFP_KERNEL );
if( bank == NULL )
return -1;
memset( bank, 'A',count );
if( copy_to_user( buf, bank, count ) ){
retval = -EFAULT;
goto out;
}
retval += count;
*(bank+count)=0;
printk( KERN_NOTICE"hello: user read %d bytes from me. %s\n",count,bank );
out:
kfree(bank);
return retval;
}
/* write to hello device */
ssize_t hello_write( struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos ){
ssize_t retval = 0;
char *bank = kmalloc( count ,GFP_KERNEL );
if( bank == NULL )
return retval;
if( copy_from_user(bank, buf, count ) ){
retval = -EFAULT;
printk( KERN_NOTICE"hello: write error\n" );
goto out;
}
retval += count;
printk( KERN_NOTICE"hello: user has written %d bytes to me: %s\n",count,
bank );
out:
kfree(bank );
return retval;
}
/* register the init and exit routine of the module */
//加载模块时 所执行的函数
module_init( hello_init );
//模块被卸载时 所执行的函数
module_exit( hello_exit );
MODULE_AUTHOR( “xiaobenzhu” ); 模块的作者
MODULE_LICENSE( “Dual BSD/GPL” ); 模块的版权
module_init( hello_init ); 加载模块时所执行的函数
module_exit( hello_exit ); 模块被卸载时所执行的函数
编写Makefile文件
#将hello.c编译成模块
obj-m:= hello.o
CURRENT_PATH:= $(shell pwd)
LINUX_KERNEL:= $(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
使用 make命令编译
使用insmod命令加载模块
使用 cat /dev/devices | grep hello 查看设备主设备号
mknod创建字符设备驱动文件hello0
编写测试程序
/* test_hello.c */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#define READ_SIZE 16
#define DEMO_DEV_NAME "/dev/hello0"
int main(int argc, char **argv){
int fd,count;
char buf[READ_SIZE+1];
if( argc<2 ){
printf( "[Usage: test device_name ]\n" );
exit(0);
}
if(( fd=open(argv[1], O_RDWR))<0){
printf( "Error:can not open the device: %s\n",argv[1] );
exit(1);
}
printf("%s has been opened: (fd:%d).\n",argv[1],fd );
if( (count = read(fd,buf,READ_SIZE ))<0 ){
perror("read error.\n");
exit(1);
}
printf( "read %d bytes from %s:%s\n",count,argv[1],buf );
memcpy( buf,"Hello",6 );
if( (count = write( fd, buf ,6 ))<0 ){
perror("write error.\n");
exit(1);
}
printf( "write %d bytes to %s:%s\n",count,argv[1],buf );
close(fd);
printf("close device %s\n",argv[1] );
return 0;
}
编译运行
使用dmesg命令查看系统日志
参考:https://www.cnblogs.com/xiaobenzhu/archive/2009/08/01/1536557.html