linux驱动实验

内核,shell,文件系统,应用程序构成了基本的linux结构

Kernel

内核又分为内存管理,进程管理,设备驱动程序,文件系统和网络管理等

shell

提供了一个界面,用户通过这个界面访问操作系统内核的服务。

文件系统

linux有很丰富的文件系统,Linux 中最普遍使用的文件系统是 Ext2,还有nfs,ext4,ext3等等。linux将独立的文件系统组合成为了一个层次的树状结构,新的文件系统可以通过挂载到某一个目录进行访问,这些功能的基础是虚拟文件系统VFS,linux除进程以外全部视为文件。

应用程序

上层开发者根据对应的API,或者SDK来开发的软件

linux系统将设备分成三个基本类型

  • 字符设备
  • 块设备
  • 网络接口

根据资料,我们对设备的所有操作基本上都可以简化成open、close、read、write、io control这几个操作。下面进行一个小的实验。

简单的驱动入门

创建一个文件夹

mkdir mydrvice

创建一个文件 mydrvice1.c

vim mydrvice1.c

写入以下内容

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/module.h>
 
//内核可以识别的许可证有“GPL”,“GPL v2”等等,如果模块没有显示地标记许可证,会被认为是私有开发
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("Kevin"); //模块作者
MODULE_DESCRIPTION("This is just a hello module!\n"); //模块的描述注释说明
//  MODULE_VERSION 代码修订号
//  MODULE_ALIAS 模块的别名
//  MODULE_DEVICE_TABLE 告诉用户空间模块支持的设备
 
static int __init hello_init(void)
{
        printk(KERN_EMERG "hello, init\n"); // printk可以用来调试内核,查看值
        return 0;
}
 
static void __exit hello_exit(void)
{
        printk(KERN_EMERG "hello, exit\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

编写Makefile

vim Makefile
ifneq ($(KERNELRELEASE),)
obj-m := mydrvice1.o
 
else
PWD  := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
endif

然后make一下

make

输入insmod 安装驱动程序

使用insmod安装模块,内部调用了 sys_init_module系统调用,在sys_init_module内部调用了load_module,把mydrvice1.ko创建成一个内核模块,返回一个struct module结构体,内核中便以这个结构体代表这个内核模块。

module->state(module结构体下的state枚举变量)

  • MODULE_STATE_LIVE //正常使用中
  • MODULE_STATE_COMING //正在被加载
  • MODULE_STATE_GOING //正在被卸载

load_module函数中完成模块的部分创建工作后,把状态置为 MODULE_STATE_COMING

sys_init_module函数中完成模块的全部初始化工作后(包括把模块加入全局的模块列表,调用模块本身的初始化函数),把模块状态置为MODULE_STATE_LIVE

使用rmmod工具卸载模块时,会调用系统调用 delete_module,会把模块的状态置为MODULE_STATE_GOING。

insmod mydrvice1.ko

输入dmesg

dmesg

结果输出

[ 253.346485] hello, init

输入rmmod 安装驱动程序

rmmod mydrvice1.ko

输入dmesg

dmesg

结果输出

[ 969.532449] hello, exit

字符设备驱动入门

创建char文件夹并且进入

mkdir char && cd char

编写char.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
 
static struct cdev chr_dev;
static dev_t ndev;


//linux内核中表示不同的设备是通过major 和minor number实现的,通过major和minor Number来加载相应的驱动程序。
//major number:表示不同的设备类型
//minor number:表示同一个设备的的不同分区
static int chr_open(struct inode* nd, struct file* filp)
{
	int major ;
	int minor;
	
	major = MAJOR(nd->i_rdev);
	minor = MINOR(nd->i_rdev);
	
	printk("chr_open, major = %d, minor = %d\n", major, minor);
	return 0;
}
 
static ssize_t chr_read(struct file* filp, char __user* u, size_t sz, loff_t* off)
{
	printk("chr_read process!\n");
	return 0;
}
 
struct file_operations chr_ops = {
    //指定初始化 C99标准
	.owner = THIS_MODULE, //THIS_MODULE是一个宏指向当前的本模块,#define THIS_MODULE (&__this_module)
	.open = chr_open, //应该是把open操作变成了自定义的函数了
	.read = chr_read  //应该是把read操作变成了自定义的函数了
};
 
static int demo_init(void)
{
	int ret;
	
	cdev_init(&chr_dev, &chr_ops); //字符设备的注册
	ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); //动态的申请注册一个设备号
	if(ret < 0 )
	{
		return ret;
	}
	
	printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
	ret = cdev_add(&chr_dev, ndev, 1);//添加字符设备
	if(ret < 0)
	{
		return ret;
	}
	
	return 0;
}
 
static void demo_exit(void)
{
	printk("demo_exit process!\n");
	cdev_del(&chr_dev);
	unregister_chrdev_region(ndev, 1);
}

//实现模块加载和卸载入口函数
module_init(demo_init);
module_exit(demo_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin");
MODULE_DESCRIPTION("A simple device example!");

编写Makefile

ifneq ($(KERNELRELEASE),)
obj-m := char.o
 
else
PWD  := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.*  Module.*
endif

安装模块

insmod char.ko

输入dmesg

dmesg

显示

[ 3539.169209] demo_init(): major = 241, minor = 0

以利用major,minor直接创建设备节点,输入

mknod /dev/chr_dev c 241 0

验证

[root@localhost char]# ls /dev/chr_dev/dev/chr_dev

编写test.c来验证

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
 
#define CHAR_DEV_NAME "/dev/chr_dev"
 
int main()
{
	int ret;
	int fd;
	char buf[32];
 
	fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY); //只读 | O_NDELAY?
	if(fd < 0)
	{
		printf("open failed!\n");
		return -1;
	}
    //返回的文件描述符,然后开始读
	read(fd, buf, 32);
	close(fd);
	
	return 0;
}

然后

gcc -c test.c -o test

./test

最后使用dmesg查看

[ 3853.632993] chr_read process!

控制两种模式

MODE_1 MODE_2

使用open打开设备,先write写入name,再read返回

如果是MODE_1,返回 hello name(3环)

如果是MODE_2,返回 welcome name(3环)

#include <linux/module.h>    // included for all kernel modules
#include <linux/kernel.h>    // included for KERN_INFO
#include <linux/init.h>      // included for __init and __exit macros
#include <linux/scpi_protocol.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <linux/fs.h>        // file_operation is defined in this header
 
#include <linux/device.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin");
MODULE_DESCRIPTION("Driver as a test case");
 
static int      majorNumber;
static struct   class*  test_module_class = NULL;
static struct   device* test_module_device = NULL;
#define DEVICE_NAME "test"      //定义设备名称
#define CLASS_NAME  "test_module"
 
//函数原型
static long test_module_ioctl(struct file *, unsigned int, unsigned long);
static int __init test_init(void);
static void __exit test_exit(void);
 
//linux/fs.h中的file_operations结构体列出了所有操作系统允许的对设备文件的操作。
//在我们的驱动中,需要将其中需要的函数进行实现。
//下面这个结构体就是向操作系统声明,那些规定好的操作在本模块里是由哪个函数实现的。
static const struct file_operations test_module_fo = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = test_module_ioctl, //unlocked_ioctl是由本模块中的test_module_ioctl()函数实现的
};
 
//本模块ioctl回调函数的实现
static long test_module_ioctl(struct file *file, unsigned int cmd, unsigned long param)
{
    
    
        switch(cmd)
        {
            case MODE_1:
            filp->f_pos += (int)arg;
            case MODE_2:
            filp->f_pos += (int)arg;
        }

        /* ioctl回调函数中一般都使用switch结构来处理不同的输入参数(cmd) */
        switch(cmd){
        case 0:
        {
                printk(KERN_INFO "[TestModule:] Inner function (ioctl 0) finished.\n");
                break;
        }
        default:
                printk(KERN_INFO "[TestModule:] Unknown ioctl cmd!\n");
                return -EINVAL;
        }
        return 0;
}
 
 
static int __init test_init(void){
        printk(KERN_INFO "开始进行初始化\n");
        // 在加载本模块时,首先向操作系统注册一个chrdev,也即字节设备,三个参数分别为:主设备号(填写0即为等待系统分配),设备名称以及file_operation的结构体。返回值为系统分配的主设备号。
        majorNumber = register_chrdev(0, DEVICE_NAME, &test_module_fo);
        if(majorNumber < 0){
                printk(KERN_INFO "注册主设备号失败 \n");
                return majorNumber;
        }DEVICE_NAME
        printk(KERN_INFO "注册主设备号成功 %d. \n", majorNumber);
 
        //接下来,注册设备类
        test_module_class = class_create(THIS_MODULE, CLASS_NAME);
        if(IS_ERR(test_module_class)){
                unregister_chrdev(majorNumber, DEVICE_NAME);
                printk(KERN_INFO "注册设备类失败\n");
                return PTR_ERR(test_module_class);
        }
        printk(KERN_INFO "注册设备类成功\n");
 
        //最后,使用device_create函数注册设备驱动
        test_module_device = device_create(test_module_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
        if (IS_ERR(test_module_device)){          // Clean up if there is an error
                class_destroy(test_module_class); // Repeated code but the alternative is goto statements
                unregister_chrdev(majorNumber, DEVICE_NAME);
                printk(KERN_ALERT "注册驱动失败\n");
                return PTR_ERR(test_module_device);
        }
        printk(KERN_INFO "注册模块驱动成功\n");
        return 0;
}
 
 
static void __exit test_exit(void)
{
	//退出时,依次清理生成的device,class和chrdev。这样就将系统/dev下的设备文件删除,并自动注销了/proc/devices的设备。
    printk(KERN_INFO "[TestModule:] Start to clean up module.\n");
    device_destroy(test_module_class, MKDEV(majorNumber, 0));
    class_destroy(test_module_class);
    unregister_chrdev(majorNumber, DEVICE_NAME);
    printk(KERN_INFO "[TestModule:] Clean up successful. Bye.\n");
}
 
module_init(test_init);
module_exit(test_exit);

实验

控制两种模式

MODE_1 MODE_2

使用open打开设备,先write写入name,再read返回

如果是MODE_1,返回 hello name(3环)

如果是MODE_2,返回 welcome name(3环)

编写char3.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include "test.h"

static struct cdev chr_dev;
static dev_t ndev;
static char name[32];


MODULE_LICENSE("GPL");
static int chr_open(struct inode* nd, struct file* filp)
{
	int major;
	int minor;
	
	major = MAJOR(nd->i_rdev);
	minor = MINOR(nd->i_rdev);
	
	printk("chr_open, major = %d, minor = %d\n", major, minor);
	return 0;
}

//filp是文件指针,count是请求传输的数据量,buff参数指向数据缓存,最后offp之处文件当前的访问位置。
//ssize_t xxx_read(struct file* filp,char __user* buff,size_t count,loff_t*offp);
static ssize_t chr_read(struct file* filp, char __user* buff, size_t count, loff_t* off)
{
	int j;
	for (j = 0; j < count; j++ )
	{   
         buff[j] = name[j];
    }
	return 0;
}

static ssize_t chr_write(struct file* filp,const char __user* buff,size_t count,loff_t* off)
{
	int i;
	printk( "write--%ld\n", count );
	printk( "write--%s\n", buff );
	for (i = 0; i < count; i++ )
	{
		name[i] = buff[i];
	}
	printk( "write--name = %s\n", name );
    return 0;
}

//本模块ioctl回调函数的实现
long char_ioctl(struct file *filp, unsigned int cmd, unsigned long param)
{
	int i;
	//ioctl回调函数中一般都使用switch结构来处理不同的输入参数(cmd)
	printk("char_ioctl: cmd=%d\n", cmd);
	switch(cmd)
    {
        case MODULE_ONE:
			printk("1\n");
			for(i=31 ; i>=0 ;i--){
				if(name[i]!="\0"){
					name[i+6] = name[i];
				}
			}
			name[0]='H'; //没想到好的算法,暂时就先这样赋值
			name[1]='e';
			name[2]='l';
			name[3]='l';
			name[4]='o';
			name[5]=' ';
			break;			
        case MODULE_TWO:
			printk("2\n");
			for(i=31 ; i>=0 ;i--){
                if(name[i]!="\0"){
                    name[i+8] = name[i];
                }
            }
            name[0]='W';
            name[1]='e';
            name[2]='l';
            name[3]='c';
            name[4]='o';
            name[5]='m';
			name[6]='e';
			name[7]=' ';
			break;
    }
	
    return 0;
}

struct file_operations chr_ops = {
	.owner = THIS_MODULE,
	.open = chr_open, 
	.read = chr_read,
    .write = chr_write,
	.unlocked_ioctl = char_ioctl
};
 
static int demo_init(void)
{
	int ret;
	
	cdev_init(&chr_dev, &chr_ops); //字符设备的注册
	ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); //动态的申请注册一个设备号
	if(ret < 0 )
	{
	    printk("wrong 2\n");
		return ret;
	}
	
	printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
	ret = cdev_add(&chr_dev, ndev, 1);//添加字符设备
	if(ret < 0)
	{
	    printk("wrong 2\n");
		return ret;
	}
	
	return 0;
}
 
static void demo_exit(void)
{
	printk("demo_exit process!\n");
	cdev_del(&chr_dev);
	unregister_chrdev_region(ndev, 1);
}

//实现模块加载和卸载入口函数
module_init(demo_init);
module_exit(demo_exit);

编写test.h

#ifndef SCULL_H_
#define SCULL_H_

//定义幻数
#define SCULL_IOC_MAGIC '$'

//定义命令->
//数据清零
#define MODULE_ONE _IO(SCULL_IOC_MAGIC, 0)
#define MODULE_TWO _IO(SCULL_IOC_MAGIC, 1)

#endif

编写测试案例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "test.h"

#define CHAR_DEV_NAME "/dev/chr_dev"
 
int main()
{
	int ret;
	int fd;
	char name[32]={"Kevin"};
 
    char temp[50];
	fd = open(CHAR_DEV_NAME, O_RDWR);

	//fd = open(CHAR_DEV_NAME, O_RDWR);
	if(fd < 0)
	{
		printf("open failed!\n");
		return -1;
	}
    //返回的文件描述符,然后开始读
	write(fd, name, 32);
    
	int r = ioctl(fd, MODULE_ONE);
	printf("r=%d\n", r);
	read(fd, temp, 32);
	printf("\n%s\n", temp);

	write(fd, name, 32);
	ioctl(fd, MODULE_TWO);
	read(fd, temp, 32);
	printf("\n%s\n", temp);

	close(fd);
	
	return 0;
}

编写Makefile

ifneq ($(KERNELRELEASE),)
obj-m := char3.o
 
else
PWD  := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.*  Module.*
endif

然后生成

make

再安装模块

insmod char3

以利用major,minor直接创建设备节点,输入

mknod /dev/chr_dev c 237 0

编译测试程序

gcc test.c -o test

测试

./test

输出

[root@localhost char2]# ./testr=0Hello KevinWelcome Kevin

在dmesg日志里面

[ 6416.836095] chr_open, major = 237, minor = 0
[ 6416.836097] write--32
[ 6416.836098] write--Kevin
[ 6416.836098] write--name = Kevin
[ 6416.836099] char_ioctl: cmd=9216
[ 6416.836099] 1
[ 6416.836155] write--32
[ 6416.836155] write--Kevin
[ 6416.836156] write--name = Kevin
[ 6416.836157] char_ioctl: cmd=9217
[ 6416.836157] 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值