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 elsePWD  := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall:	$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:	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 elsePWD  := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall:	$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:	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

Guess you like

Origin blog.csdn.net/qq_43553690/article/details/121146899