系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
设备驱动程序是内核的一部分,它完成以下的功能:
1. 对设备初始化和释放。在Linux操作系统下有三类主要的设备文件类型:
一是字符设备,二是块设备,三是网络设备。
在linux中设备文件在/dev的目录下(要分清这些设备文件的类型)
1,普通文件类型
2,目录文件类型
在linux中,它的思想是一切皆是文件,目录文件也就是Windows中的目录,也就是能用 cd 命令进入的。第一个属性为 [d],例如 [drwxr-xr-x]。
3,字符设备文件
即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]。
4,块设备文件
即存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。第一个属性为 [b]。
5,套接字文件
这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。第一个属性为 [s],最常在 /var/run目录中看到这种文件类型。
6,管道文件
FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p]。
7,链接文件
类似Windows下面的快捷方式。第一个属性为 [l],例如 [lrwxrwxrwx]。
其实系统默认情况下就已经生成了很多设备文件,但有时候我们需要自己手动新建一些设备文件,这个时候就会用到像mknod这样的命令。
mknod 的标准形式为: mknod DEVNAME {b | c} MAJOR MINOR 1,DEVNAME是要创建的设备文件名,如果想将设备文件放在一个特定的文件夹下, 就需要先用mkdir在dev目录下新建一个目录; 2, b和c 分别表示块设备和字符设备: b表示系统从块设备中读取数据的时候, 直接从内存的buffer中读取数据,而不经过磁盘; c表示字符设备文件与设备传送数据的时候是以字符的形式传送, 一次传送一个字符,比如打印机、终端都是以字符的形式传送数据; 3,MAJOR和MINOR分别表示主设备号和次设备号: 为了管理设备,系统为每个设备分配一个编号,一个设备号由主设备号和次设备号组成。 主设备号标示某一种类的设备,次设备号用来区分同一类型的设备。 linux操作系统中为设备文件编号分配了32位无符号整数, 其中前12位是主设备号,后20位为次设备号, 所以在向系统申请设备文件时主设备号不好超过4095,次设备号不好超过2^20 -1。
下面,我们就可以用mknod命令来申请设备文件了。
mknod /dev/test c 128 512
需要用到make命令
安装GCC、G++后才有make命令
首先介绍一下头文件
#include <asm/uaccess.h> #include <linux/module.h> #define THIS_MODULE (&__this_module)上面这一段定义了一些版本信息,虽然用处不是很大,但也必不可少。
#include <linux/types.h> //基本的类型定义 #include <linux/fs.h> //文件系统 #include <linux/mm.h> //内存管理 #include <linux/errno.h> //错误码 #include <asm/segment.h>// 汇编语言的一段程序
#include <linux/types.h> //基本的类型定义 #include <linux/fs.h> //文件系统 #include <linux/mm.h> //内存管理 #include <linux/errno.h> //错误码 #include <asm/segment.h>// 汇编语言的一段程序 #include <asm/uaccess.h> #include <linux/module.h> #define THIS_MODULE (&__this_module) unsigned int test_major = 0; //定义主设备号(通过系统自动获取主设备号) //定义一个读函数,对应read系统调用。(static表示该函数在本文件有效) char temp[64]= {0}; static int read_test(struct file *file, char *buf, int count, loff_t *offt) { int left; if (access_ok(VERIFY_WRITE, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。 return -EFAULT; for(left = count ; left > 0 ; left--) { __put_user(1, buf); //把内核空间的数据拷贝到用户空间buf中 buf++; } printk(KERN_EMERG "test: OK %d\n",count); return count; } /// static int write_test( struct file *file, const char *buf, int count ,struct inode *inode) { if (access_ok(4, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。 return -EFAULT; elseprintk("read_ok\n"); if(copy_from_user(temp,buf,count)) return -EFAULT; else { printk("kernelSpace temp is : %s\n",temp); printk(KERN_EMERG "testwriter: OK %d\n",count); } return count; } static int open_test(struct inode *inode, struct file *file ) { //MOD_INC_USE_COUNT; //注册到内核后,模块计数加一 try_module_get(THIS_MODULE); return 0; } static void release_test(struct inode *inode, struct file *file ) { //MOD_DEC_USE_COUNT; //模块数减一 module_put(THIS_MODULE); } // struct file_operations test_fops = { .owner = THIS_MODULE, .read = read_test, .write = write_test, .open = open_test, .release = release_test }; / int init_module1(void) //int module_iit(void) { int result; result = register_chrdev(0, "test", &test_fops); //注册字符型设备到内核中 if (result < 0) { printk(KERN_INFO "test: can't get major number\n"); //KERN_INFO打印信息 return result; } else { printk("test get success major\n"); } if (test_major == 0) test_major = result; /* 获取系统默认的主设备号 */ if(test_major == 0) { printk("test_major == 0\n"); } else { printk("test_major != 0 test_major = %d \n",test_major); } return 0; } void cleanup_module1(void) //void module_exit(void) { unregister_chrdev(test_major,"test"); // printk(“%u has been relased”,test_major); } module_init(init_module1); module_exit(cleanup_module1);每个程序都有入口和出口,我们写的C语言程序入口函数是main函数,那么驱动程序也有自己的入口和出口; module_init(init_module1);
int init_module1(void) //int module_init(void) { int result; result = register_chrdev(0, "test", &test_fops); //注册字符型设备到内核中 if (result < 0) { printk(KERN_INFO "test: can't get major number\n"); //KERN_INFO打印信息 return result; } else { printk("test get success major\n"); } if (test_major == 0) test_major = result; /* 获取系统默认的主设备号 */ if(test_major == 0) { printk("test_major == 0\n"); } else { printk("test_major != 0 test_major = %d \n",test_major); } return 0; }
static int read_test(struct file *file, char *buf, int count, loff_t *offt) { int left; if (access_ok(VERIFY_WRITE, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据 return -EFAULT; for(left = count ; left > 0 ; left--) { __put_user(1, buf); //把内核空间的数据拷贝到用户空间buf中 buf++; } printk(KERN_EMERG "test: OK %d\n",count); return count; }
linux中printf函数是把内容输出到标准输出设备中,printk则是把内容输出到内核日志中 ;
我们可以用dmesg命令查看内核日志的信息
语法
dmesg [-cn][-s <缓冲区大小>]
参数说明:
-c 显示信息后,清除ring buffer中的内容。
-s<缓冲区大小> 预设置为8196,刚好等于ring buffer的大小。
-n 设置记录信息的层级。
static int write_test( struct file *file, const char *buf, int count ,struct inode *inode) { if (access_ok(4, buf, count) == -EFAULT ) //验证是否可以向buf中写入数据。 return -EFAULT; else printk("read_ok\n"); if(copy_from_user(temp,buf,count)) return -EFAULT; else { printk("kernelSpace temp is : %s\n",temp); printk(KERN_EMERG "testwriter: OK %d\n",count); } return count; }
gcc testRead.c -o testRead
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int testdev; int i; char buf[10]; testdev = open("/dev/test",O_RDWR); if ( testdev == -1 ) { printf("Cann't open file \n"); exit(0); } read(testdev,buf,10); for (i = 0; i < 10;i++) printf("%d\n",buf[i]); close(testdev); return 0; }
gcc testWrite.c -o testWrite
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int testdev; int i; char buf[20] = "Hello World!"; testdev = open("/dev/test",O_RDWR); if ( testdev == -1 ) { perror("error is :"); exit(0); } write(testdev,buf,strlen(buf)); close(testdev); return 0; }
#KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) obj-m := -DEXPORT_SYMTAB obj-m := testDriver.o #在编绎内核本身export-objs中的文件时,make会增加一个"-DEXPORT_SYMTAB"编绎标志,它使源文件嵌入mo#dversions.h文件,将EXPORT_SYMBOL宏展开中的函数名字符串进行版本名扩展;同时,它也定义_set_ver()宏#为一空操作,使代码中的函数名不受其影响。 #在编绎模块时,make会增加"-include=linux/modversion.h -DMODVERSIONS"编绎标志,使模块中代码的函##数名得到相应版本扩展。 all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
会打印出来很多日志信息
查看test: ls /dev
删除它
在Makefile和testDriver所在的目录中打开终端,或者cd到这个目录下
make编译
如果有警告不用理会
到这里就全部演示完毕