手头有本《操作系统 原理技术与编程》(蒋静,徐志伟著),做里面的驱动程序编写的例子,原书基于kernel 2.4,和现在相去甚远,所以又参考了《Linux设备驱动程序》(Jonathan Cprbet等著),加上网上的若干资料,以及师兄的帮助,终于算是搞定了,写程序记录一下。
目的:编写驱动程序,可以观察其有效性
0. 软件基础
1. 硬件设施
由于要求可观察,就用一个电阻(10K欧)和一个发光二极管拧在一起,插进机箱上的25针并口中,如果电平不一致(输入端为高电平,输出端为低电平),则二极管可以发光。就不上图了,有材料拧一下就好。 查阅文章【3】并测试,将输入口插到数据口,输出口插到接地口,使用【3】中的测试代码运行成功。2. 驱动编写
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
static ssize_t light_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t light_write(struct file *, const char __user *, size_t,
loff_t *);
static int light_open(struct inode *, struct file *);
static int light_release(struct inode *, struct file *);
static long light_unlocked_ioctl(struct file *, unsigned int, unsigned long);
struct file_operations light_fops = {
.owner = THIS_MODULE,
.read = light_read,
.write = light_write,
.open = light_open,
.release = light_release,
.unlocked_ioctl = light_unlocked_ioctl,
// all others are NULL
};
#define BUFFER_SIZE 64
#define SUCCESS 0
#include <asm-generic/uaccess.h>
#include <linux/timer.h>
#include "light.h"
/********* light status *******/
enum light_enum {
ALL_ZERO = 1, // every time got zero
ALL_ONE = 3, // every time got ONE
ALTERNATIVE = 5, // got 1,0,1,0...
};
static enum light_enum light_flag = ALTERNATIVE;
static int lastword = 0;
static int Device_Open = 0;
static int BASE_ADDR = 0x378;
extern struct file_operations light_fops;
/******************************/
static struct timer_list light_timer;
static int SLEEP_INTERVAL = 500;
int rounds = 1;
void light_timer_callback(unsigned long data) {
char output;
// printk("my_timer_callback called (%ld).\n", jiffies);
switch (light_flag) {
case ALL_ONE:
output = 0xff;
break;
case ALL_ZERO:
output = 0;
break;
case ALTERNATIVE:
output = lastword == 0 ? 0 : 0xff;
lastword ^= 1;
break;
default:
printk("see error flag.\n");
return;
}
outb(output, BASE_ADDR);
// need to send a 0x00 signal to stop that
mod_timer(&light_timer,
jiffies + msecs_to_jiffies(SLEEP_INTERVAL * rounds));
}
/******************************/
/******* light functions *******/
// read the round number, nothing to do with count
static ssize_t light_read(struct file * file, char __user * buf, size_t count,
loff_t * pos) {
int len;
char kbuf[BUFFER_SIZE];
printk("read inside the kernel\n");
if (!Device_Open) {
printk("device not opened\n");
return -1;
}
if (rounds > 100) {
printk("rounds too big\n");
return -1;
}
kbuf[0] = (char) rounds;
kbuf[1] = 0;
len = 1;
copy_to_user(buf, kbuf, len);
printk("read %d chars from kernel\n", len);
return len;
}
// write the type, not implemented yet
static ssize_t light_write(struct file * file, const char __user * buf,
size_t count, loff_t * pos) {
char kbuf[BUFFER_SIZE];
int tmp;
printk("write inside the kernel\n");
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
if (!Device_Open) {
printk("device not opened\n");
return -1;
}
if (count == 0) {
return -EFAULT;
}
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
tmp = kbuf[0];
if (0 < tmp && tmp < 100) {
printk("modify interval rounds from %d to %d\n", rounds, tmp);
rounds = tmp;
}
return 1;
}
static int light_open(struct inode * inode, struct file * file) {
if (Device_Open)
return -EBUSY;
Device_Open++;
try_module_get(THIS_MODULE);
return SUCCESS;
}
static int light_release(struct inode * inode, struct file * file) {
Device_Open--;
module_put(THIS_MODULE);
return 0;
}
// set flat to ALL_ONE, ALL_ZERO or ALTERNATIVE
static long light_unlocked_ioctl(struct file * file, unsigned int cmd,
unsigned long arg) {
switch (cmd) {
case ALL_ONE:
case ALL_ZERO:
case ALTERNATIVE:
printk("change status from %d to %d\n", light_flag, cmd);
light_flag = cmd;
break;
default:
printk("meet error %d, %ld\n", cmd, arg);
return -1;
}
return SUCCESS;
}
/********* light end *******/
/********* dev control *******/
static int Major;
#define DEVICE_NAME "light_test"
int light_init(void) {
Major = register_chrdev(0, DEVICE_NAME, &light_fops);
if (Major < 0) {
printk("Registering the character device failed with %d/n", Major);
return Major;
}
printk("create a dev file with mknod /dev/? c %d 0.\n", Major);
printk("ZERO %d, ONE %d, ALTER %d\n", ALL_ZERO, ALL_ONE, ALTERNATIVE);
setup_timer( &light_timer, light_timer_callback, 0);
mod_timer(&light_timer,
jiffies + msecs_to_jiffies(SLEEP_INTERVAL * rounds));
return 0;
}
void light_exit(void) {
int ret = 1;
while (ret) {
ret = del_timer(&light_timer);
printk("The timer is still in use...\n");
}
printk("unregister_chrdev: %d\n", Major);
unregister_chrdev(Major, DEVICE_NAME);
}
/********* dev control end*******/
module_init(light_init);
module_exit(light_exit);
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m := light.o
else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build
default:
$(MAKE) -C $(KDIR) M=$$PWD modules
endif
3. 测试程序编写
test.c代码如下:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void main() {
char buf[10];
FILE* fp;
int fd;
int round;
while (1) {
scanf("%d", &round);
if (round == 0) {
fp = fopen("/dev/light", "r");
if (fp == NULL)
return;
fread(buf, sizeof(char), 1, fp);
printf("read round: %d\n", buf[0]);
fclose(fp);
} else if (round > 0) {
buf[0] = (char) round;
fp = fopen("/dev/light", "w");
if (fp == NULL)
return;
round = fwrite(buf, sizeof(char), 1, fp);
printf("write count %d\n", round);
fclose(fp);
} else {
if (round >= -100) {
printf("trying to call the control function, %d\n", round);
if ((fd = open("/dev/light", O_SYNC)) < 0) {
printf("error in opening the device\n");
return;
}
ioctl(fd, -round, 0);
close(fd);
} else {
break;
}
}
}
}
输入0,则查看当前的sleep round
输入>0的数字,则更改sleep round
输入-1,-3,-5则调整显示的测路[不亮、全亮、交替]
4. 测试
make
mknod /dev/light c 248 0
insmod ./light.ko
gcc -o test test.c
./test
// 下面是自己输入的测试
rmmod light
5. 测试结果
本机【fedora16】查看printk的输出结果,使用tail -f /var/log/messages观察
同时观察插在并口上的发光二极管,可以发现其工作状态正常,如:
./test之后,输入
0,可以得到round值
10,可以设置round为10(默认为1),可以发现灯光亮和暗的持续时间变长
-1,发现灯长灭
-3,灯长亮
-5,灯交替亮灭
其他信息可以在/var/log/messages中观察到
5. 注意事项
ioctl函数,参见文章[4]: “应该用unlocked_ioctl代替ioctl,不能被compat_ioctl字面意思所迷惑,用户调用时候还是用ioctl不用变。”
ioctl调用,本来不是1,3,5的结构,而是1,2,3。但是我在使用ioctl传(fd, 2, 0)的时候,light_unlocked_ioctl不会被调用,而(fd, 1/3/4/5..., 0)都可以,不知为什么
-->> 原因见[5][6],符号冲突,使用生僻符号应该可以解决此问题
参考:
1. 《操作系统 原理技术与编程》(蒋静,徐志伟著)
2. 《Linux设备驱动程序》(Jonathan Cprbet等著,魏永明等译)
3. 并行端口操作http://www.igefo.com/post-686.html
4. ioctl,unlocked_ioctl 的问题 http://zhidao.baidu.com/question/420928958.html
5. ioctl is not called if cmd = 2 http://stackoverflow.com/questions/10071296/ioctl-is-not-called-if-cmd-2
6. linux kernel code http://lxr.linux.no/linux+v3.3.1/include/asm-generic/ioctl.h#L83 http://lxr.linux.no/linux+v3.3.1/+code=FIONCLEX
7. 2.6 内核中的计时器和列表 http://www.ibm.com/developerworks/cn/linux/l-timers-list/index.html