Chinaunix首页 | 论坛 | 博客
  • 博客访问: 10492516
  • 博文数量: 2905
  • 博客积分: 20098
  • 博客等级: 上将
  • 技术积分: 36298
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-23 05:00
文章存档

2012年(1)

2011年(3)

2009年(2901)

分类: LINUX

2009-03-23 11:22:19

内核版本: 2.4.22 
阅读此文的目的学会编写Linux设备驱动。 
阅读此文的方法阅读以下2个文件: hello.c,asdf.c 
此文假设读者
已经能用C语言编写Linux应用程序
理解"字符设备文件块设备文件主设备号次设备号", 
会写简单的Shell脚本和Makefile 

1. "hello.c" 
-------------------------------- 
/* 
这是我们的第一个源文件, 
它是一个可以加载的内核模块, 
加载时显示"Hello,World!" 
卸载时显示"Bye!" 

需要说明一点,写内核或内核模块不能用写应用程序时的系统调用或函数库, 
因为我们写的就是为应用程序提供系统调用的代码。 

内核有专用的函数库,如
现在还没必要了解得很详细, 
这里用到的printk的功能类似于printf 

* "/usr/src/linux"
是你实际的内核源码目录的一个符号链接, 
如果没有现在就创建一个,因为下面和以后都会用到。 

编译它用"gcc -c -I/usr/src/linux/include hello.c" 
如果正常会生成文件hello.o, 

加载它用"insmod hello.o", 
只有在文本终端下才能看到输出。 

卸载它用"rmmod hello" 
*/ 

/* 
小技巧在用户目录的.bashrc里加上一行
* alias mkmod='gcc -c -I/usr/src/linux/include' 
然后重新登陆Shell, 
以后就可以用"mkmod hello.c"的方式来编译内核模块了。 
*/ 

/* 
开始例行公事 */ 
#ifndef __KERNEL__ 
# define __KERNEL__ 
#endif 
#ifndef MODULE 
# define MODULE 
#endif 

#include  
#include  

MODULE_LICENSE("GPL"); 
#ifdef CONFIG_SMP 
#define __SMP__ 
#endif 
/* 
结束例行公事 */ 

#include  /* printk()
在这个文件里 */ 

static int 
init_module 
(){ 
printk("Hello,World!\n"); 
return 0; /* 
如果初始工作失败,就返回非0 */ 


static void 
cleanup_module 
(){ 
printk("Bye!\n"); 

------------------------------------ 

2. "asdf.c" 
------------------------------------ 
/* 
这个文件是一个内核模块。 
内核模块的编译,加载和卸载在前面已经介绍了。 

这个模块的功能是,创建一个字符设备。 
这个设备是一块4096字节的共享内存。 
内核分配的主设备号会在加载模块时显示。 
*/ 

/* 
开始例行公事 */ 
#ifndef __KERNEL__ 
# define __KERNEL__ 
#endif 
#ifndef MODULE 
# define MODULE 
#endif 

#include  
#include  

#ifdef CONFIG_SMP 
#define __SMP__ 
#endif 
MODULE_LICENSE("GPL"); 
/* 
结束例行公事 */ 

#include  /* copy_to_user(), copy_from_user */ 
#include  /* struct file_operations, register_chrdev(), ... */ 
#include  /* printk()
在这个文件里 */ 
#include  /* 
和任务调度有关 */ 
#include  /* u8, u16, u32 ... */ 

/* 
关于内核功能库,可以去网上搜索详细资料, 
*/ 

/* 
文件被操作时的回调功能 */ 
static int asdf_open (struct inode *inode, struct file *filp); 
static int asdf_release (struct inode *inode, struct file *filp); 
static ssize_t asdf_read (struct file *filp, char *buf, size_t count,loff_t *f_pos); 
static ssize_t asdf_write (struct file *filp, const char *buf, size_t count,loff_t *f_pos); 
static loff_t asdf_lseek (struct file * file, loff_t offset, int orig); 

/* 
申请主设备号时用的结构linux/fs.h里定义 */ 
struct file_operations asdf_fops = { 
open: asdf_open, 
release: asdf_release, 
read: asdf_read, 
write: asdf_write, 
llseek: asdf_lseek, 
}; 

static int asdf_major; /* 
用来保存申请到的主设备号 */ 
static u8 asdf_body[4096]="asdf_body\n"; /* 
设备 */ 

static int 
init_module 
(){ 
printk ("Hi, This' A Simple Device File!\n"); 
asdf_major = register_chrdev (0, "A Simple Device File", &asdf_fops); /* 
申请字符设备的主设备号 */ 
if (asdf_major < 0) return asdf_major; /* 
申请失败就直接返回错误编号 */ 
printk ("The major is:%d\n", asdf_major); /* 
显示申请到的主设备号 */ 
return 0; /* 
模块正常初始化 */ 


static void 
cleanup_module 
(){ 
unregister_chrdev(asdf_major, "A Simple Device File"); /* 
注销以后,设备就不存在了 */ 
printk("A Simple Device has been removed,Bye!\n"); 


/* 
编译这个模块然后加载它
如果正常,会显示你的设备的主设备号。 
现在你的设备就建立好了,我们可以测试一下。 
假设你的模块申请到的主设备号是254, 
运行"mknod abc c 254 0",就建立了我们的设备文件abc 
可以把它当成一个4096字节的内存块来测试一下
比如"cat abc", "cp abc image", "cp image abc", 
或写几个应用程序用它来进行通讯。 

介绍一下两个需要注意的事
一是printk()的显示只有在非图形模式的终端下才能看到
二是加载过的模块最好在不用以后卸载掉。 

如果对Linux环境的系统调用很陌生,建议先看APUE这本书。 
*/ 

static int 
asdf_open /* open
回调 */ 

struct inode *inode, 
struct file *filp 
){ 
printk("^_^ : open %s\n ",\ 
current->comm); 
/* 
应用程序的运行环境由内核提供,内核的运行环境由硬件提供。 
这里的current是一个指向当前进程的指针
现在没必要了解current的细节。 
在这里,当前进程正打开这个设备
返回0表示打开成功,内核会给它一个文件描述符。 
这里的comm是当前进程在Shell下的command字符串。 
*/ 
return 0; 


static int 
asdf_release /* close
回调 */ 

struct inode *inode, 
struct file *filp 
){ 
printk("^_^ : close\n "); 
return 0; 


static ssize_t 
asdf_read /* read
回调 */ 

struct file *filp, 
char *buf, 
size_t count, 
loff_t *f_pos 
){ 
loff_t pos; 
pos = *f_pos; /* 
文件的读写位置 */ 
if ((pos==4096) || (count>4096)) return 0; /* 
判断是否已经到设备尾,或写的长度超过设备大小 */ 
pos = count; 
if (pos > 4096) { 
count -= (pos - 4096); 
pos = 4096; 

if (copy_to_user(buf, asdf_body *f_pos, count)) return -EFAULT; /* 
把数据写到应用程序空间 */ 
*f_pos = pos; /* 
改变文件的读写位置 */ 
return count; /* 
返回读到的字节数 */ 


static ssize_t 
asdf_write /* write
回调,read一一对应 */ 

struct file *filp, 
const char *buf, 
size_t count, 
loff_t *f_pos 
){ 
loff_t pos; 
pos = *f_pos; 
if ((pos==4096) || (count>4096)) return 0; 
pos = count; 
if (pos > 4096) { 
count -= (pos - 4096); 
pos = 4096; 

if (copy_from_user(asdf_body *f_pos, buf, count)) return -EFAULT; 
*f_pos = pos; 
return count; 


static loff_t 
asdf_lseek /* lseek
回调 */ 

struct file * file, 
loff_t offset, 
int orig 
){ 
loff_t pos; 
pos = file->f_pos; 
switch (orig) { 
case 0: 
pos = offset; 
break; 
case 1: 
pos = offset; 
break; 
case 2: 
pos = 4096 offset; 
break; 
default: 
return -EINVAL; 

if ((pos>4096) || (pos<0)) { 
printk("^_^ : lseek error %d\n",pos); 
return -EINVAL; 

return file->f_pos = pos; 
}
阅读(1271) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~