Press "Enter" to skip to content

Linux编写内核模块实例

本文演示了Linux加载内核模块(驱动)的过程,文中实验环境为CentOS 5.9 64bit。

1,准备工作
安装基础软件包

yum install kernel-devel.x86_64 gcc.x86_64 gcc-c++.x86_64

然后会在/usr/src/kernels/下面安装好内核源码包,最好把目录的名字改一下,以便能够与/lib/modules/$(uname -r)/目录相响应,避免/lib/modules/$(uname -r)/目录中有一些软链接找不到内核源码包位置

mv /usr/src/kernels/2.6.* /usr/src/kernels/$(uname -r)-x86_64

2,加入内核模块(驱动)
进入字符设备驱动目录

[root@localhost ~]# cd /usr/src/kernels/$(uname -r)-x86_64/drivers/char
[root@localhost char]# mkdir mycdev    #建立驱动目录
[root@localhost char]# cd mycdev/
[root@localhost char]# cat /proc/devices   #找一个未使用的字符设备主设备号,比如199,记好
[root@localhost mycdev]# vim mycdev.c    #建立内核态设备驱动程序,内容见下方
[root@localhost mycdev]# vim Makefile    #建立Makefile文件,内容见下方

mycdev.c(字符设备驱动程序)内容

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>

MODULE_AUTHOR("Edsionte Wu");
MODULE_LICENSE("GPL");

#define MYCDEV_MAJOR 199 /*the predefined mycdev's major devno*/
#define MYCDEV_SIZE 100

static int mycdev_open(struct inode *inode, struct file *fp)
{
	return 0;
}

static int mycdev_release(struct inode *inode, struct file *fp)
{
	return 0;
}

static ssize_t mycdev_read(struct file *fp, char __user *buf, size_t size, loff_t *pos)
{
	unsigned long p = *pos;
	unsigned int count = size;
	//int i;
	char kernel_buf[MYCDEV_SIZE] = "This is mycdev!";

	if(p >= MYCDEV_SIZE)
		return -1;
	if(count > MYCDEV_SIZE)
		count = MYCDEV_SIZE - p;

	if (copy_to_user(buf, kernel_buf, count) != 0) {
		printk("read error!\n");
		return -1;
	}

	/*
	for (i = 0; i < count; i++) {
		__put_user(i, buf);//write 'i' from kernel space to user space's buf;
		buf++;
	}
	*/

	printk("edsionte's reader: %d bytes was read...\n", count);
	return count;

}

static ssize_t mycdev_write(struct file *fp, const char __user *buf, size_t size, loff_t *pos)
{
	return size;
}

/*filling the mycdev's file operation interface in the struct file_operations*/
static const struct file_operations mycdev_fops =
{
	.owner = THIS_MODULE,
	.read = mycdev_read,
	.write = mycdev_write,
	.open = mycdev_open,
	.release = mycdev_release,
};

/*module loading function*/
static int __init mycdev_init(void)
{
	int ret;

	printk("mycdev module is staring..\n");

	ret=register_chrdev(MYCDEV_MAJOR,"edsionte_cdev",&mycdev_fops);
	if(ret<0)
	{
		printk("register failed..\n");
		return 0;
	}
	else
	{
		printk("register success..\n");
	}

	return 0;
}

/*module unloading function*/
static void __exit mycdev_exit(void)
{
	printk("mycdev module is leaving..\n");
	unregister_chrdev(MYCDEV_MAJOR,"edsionte_cdev");
}

module_init(mycdev_init);
module_exit(mycdev_exit);

Makefile内容

obj-m:=mycdev.o
PWD:=$(shell pwd)
CUR_PATH:=$(shell uname -r)
KERNEL_PATH:=/lib/modules/$(CUR_PATH)/build

all:
        make -C $(KERNEL_PATH) M=$(PWD) modules
clean:
        make -C $(KERNEL_PATH) M=$(PWD) clean

提示一下,在kbuild中,用M=dir用来指定生成的内核模块的存放位置

[root@localhost mycdev]# make    #将源代码编译成内核模块
[root@localhost mycdev]# insmod mycdev.ko    #插入模块,此时可以在dmesg中看到模块注册信息
[root@localhost mycdev]# mknod /dev/mycdev c 199 0    #创建设备文件结点
[root@localhost mycdev]# chmod 777 /dev/mycdev    #修改设备文件权限

3,进入用户态测试

[root@localhost ~]# cd /root
[root@localhost ~]# vim aaa.c    #建立用户态驱动文件,内容如下

aaa.c(用户态测试程序)内容

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
	int testdev;
	int i, ret;
	char buf[15];

	testdev = open("/dev/mycdev", O_RDWR);

	if (-1 == testdev) {
		printf("cannot open file.\n");
		exit(1);
	}

	if (ret = read(testdev, buf, 15) < 15) {
		printf("read error!\n");
		exit(1);
	}

	printf("%s\n", buf);

	close(testdev);

	return 0;
}
[root@localhost ~]# gcc aaa.c -o aaa    #生成二进制可执行文件
[root@localhost ~]# ./aaa
This is mycdev!

参考文档:

printf到printk的转变

动手实践字符设备驱动

Leave a Reply

Your email address will not be published. Required fields are marked *