linux 3.6 内核驱动程序编写

手头有本《操作系统 原理技术与编程》(蒋静,徐志伟著),做里面的驱动程序编写的例子,原书基于kernel 2.4,和现在相去甚远,所以又参考了《Linux设备驱动程序》(Jonathan Cprbet等著),加上网上的若干资料,以及师兄的帮助,终于算是搞定了,写程序记录一下。


目的:编写驱动程序,可以观察其有效性


由于linux本身的改动,和自己对linux不熟悉的原因,编写的过程绕了不少弯路,还是直接写结果会清晰一些。 由于是实验学习用途,所以代码可能在安全性检查上有所问题大致分为如下步骤:

0. 软件基础

       从kernel.org上下载3.6.0源代码并编译

1. 硬件设施

       由于要求可观察,就用一个电阻(10K欧)和一个发光二极管拧在一起,插进机箱上的25针并口中,如果电平不一致(输入端为高电平,输出端为低电平),则二极管可以发光。就不上图了,有材料拧一下就好。 查阅文章【3】并测试,将输入口插到数据口,输出口插到接地口,使用【3】中的测试代码运行成功。

2. 驱动编写

       light.h代码如下:



#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


       light.c代码如下:       其中定时器参考[7]
#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);

       Makefile代码如下【2】:       
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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值