Chinaunix首页 | 论坛 | 博客
  • 博客访问: 11109877
  • 博文数量: 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; 
}
阅读(1321) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~