Linux驱动入门

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

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

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

内核有专用的函数库,如 <linux/kernel.h>, <linux/fs.h>, <linux/sche.h>
现在还没必要了解得很详细,  
这里用到的 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 <linux/config.h> 
#include <linux/module.h> 

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

#include <linux/kernel.h> /* 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 <linux/config.h> 
#include <linux/module.h> 

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

#include <asm/uaccess.h> /* copy_to_user(), copy_from_user */ 
#include <linux/fs.h> /* struct file_operations, register_chrdev(), ... */ 
#include <linux/kernel.h> /* printk()
在这个文件里  */ 
#include <linux/sched.h> /* 
和任务调度有关  */ 
#include <linux/types.h> /* 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; 
}

你可能感兴趣的:(linux,职场,休闲)