韦东山Linux驱动入门实验班(1)hello驱动

news2024/12/26 21:12:48

前言

(1)学习韦东山老师的Linux,因为他讲的很精简,以至于很多人听不懂。接下来我讲介绍韦东山老师的驱动实验班的第一个Hello程序。

(2)注意,请先学习完视频再来看这个教程!本文仅供入门学习!如需深入,请搜索其他博客!

(3)gitee仓库;GitHub仓库;

代码

先上代码,代码增加了注释。如果仅看注释就能够理解的话,就没必要再花时间往下看了。具体代码在我仓库里面。

驱动代码

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是驱动层代码
 * 作者 : CSDN风正豪
*/



#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/backing-dev.h>
#include <linux/shmem_fs.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>
#include <linux/module.h>
#include <linux/uaccess.h>

static int major; //主设备号,用于最后的驱动卸载

/*
 *传入参数 :
	 *node :
	 *filp :
 *返回参数 : 如果成功返回0
*/
static int hello_open (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

/*
 *传入参数 :
	 *filp :要读的文件
	 *buf :读的数据放在哪里
	 *size :读多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :读到的数据长度	
*/
static	ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*
 *传入参数 :
	 *filp :要写的文件
	 *buf :写入的数据来自于buf
	 *size :写多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :写的数据长度	
*/
static	ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*作用 : 应用程序关闭的时候调用这个函数
 *传入参数 :
	 *node :
	 *filp :要关闭的文件
 *返回参数 :成功返回0	
*/
static	int hello_release (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

//1,构造 file_operations 
static const struct file_operations  hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release
};

//2,注册驱动(注意,我们在入口函数中注册)

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

//在命令行输入rmmod命令,就是注册驱动程序。之后就会进入这个出口函数
//4,出口函数
static void  hello_exit(void)
{
	//卸载驱动程序
	//第一个参数是主设备号,第二个是名字
    unregister_chrdev(major,"100ask_hello");
	//如果成功卸载驱动,打印
	printk("rmmod success!\n");
}


module_init(hello_init); //确认入口函数
module_exit(hello_exit); //确认出口函数

/*最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加
 *这个协议要求我们代码必须免费开源,Linux遵循GPL协议,他的源代码可以开放使用,那么你写的内核驱动程序也要遵循GPL协议才能使用内核函数
 *因为指定了这个协议,你的代码也需要开放给别人免费使用,同时可以根据这个协议要求很多厂商提供源代码
 *但是很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层
*/
MODULE_LICENSE("GPL"); //指定模块为GPL协议
MODULE_AUTHOR("CSDN:qq_63922192");  //表明作者,可以不写



应用层代码

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是应用层代码
 * 作者 : CSDN风正豪
*/



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

/* 作用:输入三个参数表示向/dev/xxx文件中写入数据,输入非三个数据,表示向/dev/xxx文件中读取数据。
 * 写 : ./hello_test /dev/xxx 100ask
 * 读 : ./hello_test /dev/xxx
 */
int main(int argc,char** argv)
{
    int fd,len;    //fd:存放文件描述符;len:存放写入字符个数
    char buf[100]; //存放文件中的字符
	//如果输入参数小于2个,打印本程序使用方法
    if(argc < 2)
    {
        printf("Usage:\n");
        printf("%s <dev> [string]\n",argv[0]);
        return -1;
    }

    //打开/dev/xxx文件(设备节点),返回一个文件描述符,我们之后可以直接根据这个文件描述符来操作设备节点,间接操作驱动
    fd = open(argv[1],O_RDWR);
	//如果打开失败
    if(fd < 0)
    {
        printf("can not open file %s\n",argv[1]);
        return -1;
    }

    //如果输入参数为3个,表示写入数据
    if(argc == 3)
    {
		//因为strlen计算字符串长度不会包括'\0',所以需要+1
        len = write(fd,argv[2],strlen(argv[2])+1);
		//打印出写入字符个数
        printf("write ret = %d\n",len);
    }
    //否则为读取数据
    else
    {
		//读入100个字符
        len =read(fd,buf,100);
		//无论传入多少个数据,最多都只会读100个字符
        buf[99] = '\0';
		//打印读取到的字符
        printf("read str : %s\n",buf);
    }

    //关闭文件
    close(fd);
    return 0;
}

前置知识

(1)因为本人也是刚学不久,所以可能会有讲的有所偏差的地方。请及时指正,谢谢了。

(2)首先我们通常都是在Linux的终端上打开一个可执行文件,然后可执行文件就会执行程序。那么这个可执行文件做了什么呢?

(3)可执行文件先是在应用层读取程序,其中会有很多库函数,库函数是属于内核之中。而内核又会往下调用驱动层程序。最终驱动层控制具体硬件。

<1>其实应用程序到库是比较容易理解的,比如我们刚学习C语言的时候,使用了printf,scanf等等这些函数。而这些函数就在库中。

<2>库可以和系统内核相连接,具体怎么实现的我也不太清楚。

<3>我们写了一个驱动程序,就需要告诉内核,这个过程叫做注册。我们注册了驱动之后,内核里面就会有这个驱动程序的信息,然后上层应用就可以调用。

(4)听起来是不是很懵,我也很懵。不过不要怕,我们只需要知道,咱们需要编写两个程序,一个是驱动层的,一个是应用层的,最后驱动层需要注册进入内核,应用层才能够使用。其他的先不要管。

(5)我们在应用层调用read函数,对应驱动层的read函数。write函数和write函数对应。open函数和open函数对应。close函数和release函数对应(这个为什么不一样我也不清楚)。

(6)我们对Linux 应用程序对驱动程序的调用流程有一个简单了解之后,我得知道整个程序编写流程应该怎么做。至于流程为什么是这样的,我们记住即可。因为这些都是人规定的,如果之后学的深了再进行深究也不迟,现在我们主要是入门。

 

驱动代码讲解

流程介绍

(1)下面我将介绍一个驱动程序编写的流程,如果看完上面的,大家还是一脸懵。没关系,我们根据代码逐个讲解,大家应该就会有一个简单的认识了

(2)流程:(注意,你看到有些人讲的流程看似可能跟我不一样,你得仔细对比,就会发现其实都是一样的,讲法不同而已)

<1>我们首先需要编写一个file_operations类型的结构体,这个结构体用于管理驱动程序。之后我们将驱动程序注册进入内核之后,我们在应用层调用这个驱动,那么就可以直接通过这个结构体来操作驱动中的open,write,read等函数。

<2>实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体。这样我们在应用层调用open,write,read等函数,就是调用这个驱动了。

这个时候有人可能会问了,有这么多个驱动,我怎么知道open对应的是哪一个驱动?很简单,咱们在写应用层程序的时候,是不是第一个参数是需要传入一个设备号。系统根据这个设备号来判断是调用的哪一个驱动。

<3>把 file_operations 结构体告诉内核: register_chrdev。我们写了一个驱动,但是内核是不知道的。那么怎么办呢?我们就去注册他,内核就明白,有了这个驱动,然后给他分配一个设备号。之后应用层就可以根据这个设备号来调用驱动层了。

<4> 这个时候,有人就有疑问了,谁来注册这个结构体?于是我们需要一个入口函数来进行注册,安装驱动程序时,就会去调用这个入口函数。

<5>有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev。

<6>最后需要加入GPL协议。因为Linux是遵顼GPL协议的,所以你如果需要使用Linux其他的驱动层函数,就必须遵顼GPL协议,强制要求开源代码。根据这个协议,你可以要求所有使用Linux的厂商提供驱动层源代码,同时别人也可以要求你公开你的驱动层代码,这个是相互的。不过很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层。至于还有一个作者名字的添加,随便写不写。

代码逐个分析

(1)第一步,咱们要创建一个file_operations结构体。这个结构体static const struct file_operations 部分是必须这么写的,规定的。最后这个结构体的名字可以不是hello_drv,随便定,不过后面register_chrdev要利用这个结构体名来注册

static const struct file_operations  hello_drv = {

};

(2)

<1>创建出来这个file_operations结构体之后,咱们就需要根据需求在这个结构体里面写入参数。对于一个文件,open,write,read,release肯定都是需要的。那么就在这几个参数中写入数据,这个数据是函数指针。(对于新手朋友可能不知道结构体".成员"是什么意思,请看C语言结构体详解的赋值部分)至于.owner  = THIS_MODULE,这个是规定要存在的,具体为什么我也不是很清楚。

<2>在写驱动程序的时候,我们需要注意,打印函数是printk,而不是printf。为什么呢?因为内核没有办法使用 C 语言库

<3>需要注意,咱们这个内核打印信息需要输入:echo "7 4 1 7" > /proc/sys/kernel/printk,打开内核打印。虽然绝大多数内核打印是默认打开的,还是需要注意。

如果不想看内核打印信息,就输入:echo "7 4 1 7" > /proc/sys/kernel/printk。(听不懂没关系,后面演示的时候会解释到)

/*
 *传入参数 :
	 *node :
	 *filp :
 *返回参数 : 如果成功返回0
*/
static int hello_open (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

/*
 *传入参数 :
	 *filp :要读的文件
	 *buf :读的数据放在哪里
	 *size :读多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :读到的数据长度	
*/
static	ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*
 *传入参数 :
	 *filp :要写的文件
	 *buf :写入的数据来自于buf
	 *size :写多大数据
	 *offset :偏移值(一般不用)
 *返回参数 :写的数据长度	
*/
static	ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return size;
}

/*作用 : 应用程序关闭的时候调用这个函数
 *传入参数 :
	 *node :
	 *filp :要关闭的文件
 *返回参数 :成功返回0	
*/
static	int hello_release (struct inode *node, struct file *filp)
{
	/*__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/
    printk("%s %s %d\n",__FILE__,__FUNCTION__, __LINE__);
    return 0;
}

static const struct file_operations  hello_drv = {
    .owner      = THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
    .release    = hello_release
};

(3)

<1>这个时候,咱们就需要将file_operations 结构体告诉内核。调用register_chrdev()函数就可以让内核知道file_operations 结构体。

<2>如果是看韦东山老师的视频的话,都知道,其实按照他的第一步,是需要确定主设备号的。不过,咱们在register_chrdev()的第一个参数输入0,那么系统将会帮我们自动确定主设备号,并且返回这个主设备号。

<3>register_chrdev()的第二个参数,我们随便起什么名字。我就跟着韦东山老师的一样算了,懒得改了。

<4>register_chrdev()的第三个参数,这个就跟咱们之前定义的file_operations 结构体有关了。因为我们上面定义的是static const struct file_operations  hello_drv;结构体名为hello_drv,所以第三个参数输入hello_drv。

<5>这个返回的主设备号,我们需要接收,因为之后卸载程序的时候需要使用到。但是如果你不打算卸载,不接收这个主设备号也可以

(4)

<1>将file_operations 结构体告诉内核以后,我们还需要注册驱动程序,就需要一个入口函数,这个入口函数需要module_init(xxx)这个宏来告诉内核,确认入口函数。xxx就是我们入口函数名。

<2>那么问题来了,我们什么时候才知道,注册了驱动程序呢?很简单,当我们在Linux终端输入insmod hello_drv.ko程序的时候,那么系统就会调用hello_init()函数。

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

module_init(hello_init); //确认入口函数

(5)咱们有了入口程序,必然也有一个出口程序。这个时候就需要module_exit(XXX)来确认出口函数了。

//在命令行输入rmmod命令,就是注册驱动程序。之后就会进入这个出口函数
//4,出口函数
static void  hello_exit(void)
{
	//卸载驱动程序
	//第一个参数是主设备号,第二个是名字
    unregister_chrdev(major,"100ask_hello");
	//如果成功卸载驱动,打印
	printk("rmmod success!\n");
}

module_exit(hello_exit); //确认出口函数

(6)最后,添加GPL协议和作者名。GPL协议是强制要求必须添加的,作者名随便。

/*最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加
 *这个协议要求我们代码必须免费开源,Linux遵循GPL协议,他的源代码可以开放使用,那么你写的内核驱动程序也要遵循GPL协议才能使用内核函数
 *因为指定了这个协议,你的代码也需要开放给别人免费使用,同时可以根据这个协议要求很多厂商提供源代码
 *但是很多厂商为了规避这个协议,驱动源代码很简单,复杂的东西放在应用层
*/
MODULE_LICENSE("GPL"); //指定模块为GPL协议
MODULE_AUTHOR("CSDN:qq_63922192");  //表明作者,可以不写

命令行操作讲解

命令行操作流程

(1)命令行输入流程:

<1>根据上面驱动代码讲解,我们知道了,将驱动代码编写出来以后,内核是不知道的,还需要注册。而注册方法有两种,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“insmod”命令加载驱动模块在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进Linux 内核中。
<2>我们需要确认是否注册成功,所以需要输入cat /proc/devices:查看当前已经被使用掉的设备号。lsmod,查看内核中已经加载的驱动程序

<3>创建设备节点。驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作

<4>设备节点创建之后,我们就可以按照:应用程序   /dev/xxx(xxx是设备节点,名字任意)格式来从应用层调用驱动层了。

<5>用完之后,咱们可能想要卸载了,就可以输入:rmmod 驱动程序 来卸载。

(2)现在我们知道应用程序如何通过设备节点调用驱动程序了。那么现在我们讲一下需求。我们想,如果在命令行输入三个参数,那么表示写入数据。如果输入两个参数,就表示读取数据。

Makefile代码

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hello_test hello_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_test

obj-m	+= hello_drv.o

应用层代码讲解

(1)咱们知道了知道了需求之后就要进行操作了。不过为了防止有部分人基础过差,还是解释一下main函数的argc和argv是什么。

(2)我们在命令行输入./hello_test  /dev/xxx  100ask的时候,会将这三个存入到一个argv的二级指针里面。这个argv[0]存放./hello_test,argv[1]存放/dev/xxx,argv[2]存放100ask。而argc存放传入的参数有几个,比如现在就是传入三个参数了。但是有人会问了,我怎么知道是三个参数呢?这个很简单,用空格区分参数。

(3)

<1>如果传入参数少于2个,那么我们就会打印出这个应用程序的使用方法。同时结束程序。

<2>使用open打开这个设备节点,之后会返回一个文件描述符,我们之后就可以根据这个文件描述符来操作这个设备节点。同时,内核会调用驱动层的hello_open()函数,因为应用层使用了open函数。

<3>如果没有打开这个文件,open函数将会返回-1,我们判断fd是否为-1,如果是-1那么结束程序,提醒没有打开设备节点。

<4>现在我们需要判断传入的是三个参数。如果是三个参数,那么就表示写数据,内核调用hello_write()函数,因为应用层使用了write函数。否则表示读数据,内核调用hello_read()函数,因为应用层调用了read函数。

<5>最后调用close函数,关闭文件。对应内核调用hello_release函数。

/* 说明 : 
 	*1,本代码是学习韦东山老师的驱动入门视频所写,增加了注释。
 	*2,采用的是UTF-8编码格式,如果注释是乱码,需要改一下。
 	*3,这是应用层代码
 * 作者 : CSDN风正豪
*/



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

/* 作用:输入三个参数表示向/dev/xxx文件中写入数据,输入非三个数据,表示向/dev/xxx文件中读取数据。
 * 写 : ./hello_test /dev/xxx 100ask
 * 读 : ./hello_test /dev/xxx
 */
int main(int argc,char** argv)
{
    int fd,len;    //fd:存放文件描述符;len:存放写入字符个数
    char buf[100]; //存放文件中的字符
	//如果输入参数小于2个,打印本程序使用方法
    if(argc < 2)
    {
        printf("Usage:\n");
        printf("%s <dev> [string]\n",argv[0]);
        return -1;
    }

    //打开/dev/xxx文件(设备节点),返回一个文件描述符,我们之后可以直接根据这个文件描述符来操作设备节点,间接操作驱动
    fd = open(argv[1],O_RDWR);
	//如果打开失败
    if(fd < 0)
    {
        printf("can not open file %s\n",argv[1]);
        return -1;
    }

    //如果输入参数为3个,表示写入数据
    if(argc == 3)
    {
		//因为strlen计算字符串长度不会包括'\0',所以需要+1
        len = write(fd,argv[2],strlen(argv[2])+1);
		//打印出写入字符个数
        printf("write ret = %d\n",len);
    }
    //否则为读取数据
    else
    {
		//读入100个字符
        len =read(fd,buf,100);
		//无论传入多少个数据,都只会读100个字符
        buf[99] = '\0';
		//打印读取到的字符
        printf("read str : %s\n",buf);
    }

    //关闭文件
    close(fd);
    return 0;
}

效果演示

为了让大家更深刻的理解驱动层和应用层,我在此分为两种演示方法,一种是打开内核打印的效果演示,一种是关闭内核打印的效果演示。

关闭内核打印效果演示

(1)make编译程序,注意,这个需要在Ubuntu中编译程序

(2)

<1>在开发板中,共享与Ubuntu共享文件:mount -t nfs -o nolock,vers=3  192.168.5.11:/home/book/nfs_rootfs  /mnt。

<2>之后我们关闭内核打印:echo 0       4       0      7  > /proc/sys/kernel/printk

(3)

<1>确认存在 hello_drv.ko文件

<2>insmod hello_drv.ko装载驱动

<3>lsmod 确认驱动已经安装

 (4)我们知道驱动已经安装好了,那么我们需要知道这个驱动的设备号,输入:cat /proc/devices:查看当前已经被使用掉的设备号。驱动名字与我们在驱动层使用register_chrdev()函数的第二个参数有关

(5) 创建设备节点: mknod  /dev/xyz c 240 0

<1>“/dev/xyz”是要创建的节点文件,xyz可以为任意名字

<2>“c”表示这是个字符设备

<3>“ 240”是设备的主设备号,这个数值是根据cat /proc/devices查看驱动的时候,看到240 100ask_hello,所以100ask_hello这个驱动的设备号是240。

<4>“ 0”是设备的次设备号,默认写0即可。

 (6)命令行调用

(7)

<1>最后删除驱动程序:rmmod  驱动程序 (我们的驱动程序叫做hello_drv)

<2>删除设备节点:rm /dev/xyz

 

 打开内核打印效果演示

(1)打开内核打印:echo "7 4 1 7" > /proc/sys/kernel/printk

 (2)

<1>因为名为打开了内核打印,所以会出现很多奇怪的东西,不用管。按shift+C即可。

<2>我们输入insmod hello_drv.ko装载程序,就是调用了驱动层的入口函数,所以这里内核会打印“insmod success!”

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int  hello_init(void)
{
	/*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
	 *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
	 *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
	*/
    major = register_chrdev(0,"100ask_hello",&hello_drv);
	//如果成功注册驱动,打印
	printk("insmod success!\n");
    return 0;
}

(3)

<1>确认存在 hello_drv.ko文件

<2>insmod hello_drv.ko装载驱动

<3>lsmod 确认驱动已经安装

(4)创建设备节点: mknod  /dev/xyz c 240 0

 (5)

<1>我们只输入两个参数,发现他内核成功打印了数据,从中我们可以知道调用了hello_open,hello_read,hello_release函数。

<2>我们输入三个参数,从内核打印得知, 我们调用了hello_open,hello_read,hello_release函数。与应用层相对应。

 

 (6)

<1>删除驱动程序,最后我们发现成功打印了内核信息。

<2>删除设备节点:rm /dev/xyz

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/531261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AD9680+JESD204B接口+FPGA FMC高速率数据采集板卡

板卡概述&#xff1a; 【FMC_XM155】 FMC_XM155 是一款基于 VITA57.1 标准的&#xff0c;实现 2 路 14-bit、500MSPS/1GSPS/1.25GSPS 直流耦合 ADC 同步采集 FMC 子卡模 块。 该模块遵循 VITA57.1 规范&#xff0c;可直接与 FPGA 载卡配合使用&#xff0c;板 卡 ADC 器件采用…

CIE颜色空间LCh、Lab、XYZ介绍与转换关系(包含源码)

项目场景&#xff1a; 提示&#xff1a;在颜色科学中&#xff0c;LCh和Lab是比较常用的 LCh是由MATLAB计算出的数据&#xff0c;但是我所需要在Qt的q3dsurface绘制出这个切面&#xff0c;看了Qt官方Examples&#xff0c;墨西哥草帽算法的3D模型就是由XYZ组成的。所以我需要LC…

【c语言】组件化打包—静态库

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

MacOS使用Parallels Desktop安装win11虚拟机

文章目录 前话一、安装Parallels Desktop二、安装Windows11虚拟机1.win11镜像下载2.虚拟机启动 三、创建过程问题解决1.安装win11系统的时候不显示网路2.系统安装完成后无法连接网络 三、参考文献 前话 为了让大家能快速获取到需要的内容&#xff0c;这里提前说明一下本文适用…

杂记——23.java中的值传递和应用传递

这篇文章我们来讲一下java中的值传递和引用传递 结论&#xff1a;java中只存在值传递&#xff0c;不存在引用传递&#xff08;C中有引用传递&#xff09; 分析&#xff1a; 值传递(pass by value)&#xff1a;在调用函数时&#xff0c;将实际参数复制一份传递到函数中&#…

用DevExpress WinForms富文本编辑器,集成高级文本编辑功能(二)

DevExpress WinForm富文本编辑器&#xff08;RTF编辑器&#xff09;控件允许用户将高级文本编辑功能集成到下一个WinForms项目中&#xff0c;它包括全面的文本格式选项、支持邮件合并&#xff0c;并附带了丰富的终端用户选项集&#xff0c;因此可以轻松交付受Microsoft word启发…

助力工业物联网,工业大数据之数仓维度层DWS层构建【十二】

文章目录 数仓维度层DWS层构建01&#xff1a;项目回顾02&#xff1a;项目目标03&#xff1a;维度建模回顾&#xff1a;建模流程04&#xff1a;维度建模回顾&#xff1a;维度设计05&#xff1a;维度建模回顾&#xff1a;维度模型 数仓维度层DWS层构建 01&#xff1a;项目回顾 O…

《现代中学生》期刊简介及投稿要求

《现代中学生》期刊简介及投稿要求 《现代中学生》 主管单位&#xff1a;吉林省教育厅 主办单位&#xff1a; 吉林教育杂志社 国际刊号ISSN&#xff1a;1009-5748&#xff1b;国内刊号CN&#xff1a;22-1046/G4&#xff1b;邮发代号&#xff1a;12-52 出版周期&#xff1a…

刷题day65:整数拆分

题意描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。. 思路&#xff1a; 首先dp[i]代表将i拆分&#xff0c;并得到的最大乘积&#xf…

springboot+jsp高校招生宣传系统java校园网站

学校招生管理系统的用户是系统最根本使用者&#xff0c;按需要分析系统包括两类用户&#xff1a;学生、管理员。这两类用户对系统的功能简要如下。 管理员通过后台的登录页面&#xff0c;选择管理员权限后进行登录&#xff0c;管理员的权限包括招生计划管理、招生录取信息管理和…

知识变现海哥:我们不生产知识,我们只是知识的搬运工!

现在大家都在谈知识付费&#xff0c;而且有一些东西就是书本上有的&#xff0c;但被录制成视频&#xff01;加上专业的讲师去讲&#xff0c;配上PPT文档&#xff0c;然后发布出去&#xff0c;标上一定的价格后&#xff0c;就会有人去付费购买学习。 想想是不是很暴利&#xff1…

如何使用ArcGIS生成随机数

&#xff08;本文首发于“水经注GIS”公号&#xff0c;关注公号免费领取地图数据&#xff09; 在制图的过程中&#xff0c;制作某些效果需要根据字段的随机数来生成对应的颜色&#xff0c;所以需要操作字段生成随机数&#xff0c;这里为大家介绍一下ArcGIS生成随机数的方法&…

对话金现代|轻骑兵低代码开发平台,破局企业“数字化焦虑

当今世界正经历百年未有之大变局&#xff0c;大数据、云计算、区块链、人工智能等新技术飞速发展&#xff0c;越来越多的企业经营者&#xff0c;开始投入到数字化转型的浪潮中&#xff0c;期望通过数字化帮助企业实现业务增长&#xff0c;开启无限可能。但随着时间的推移&#…

基于平台视角下的数据交易模式及特点分析

基于平台视角下的数据交易模式及特点分析 陈宏民1, 熊红林1, 胥莉1&#xff0c;杨云鹏1, 卓训方2 1 上海交通大学安泰经济与管理学院&#xff0c;上海 200030 2 上海数据交易所有限公司&#xff0c;上海 201203 摘要&#xff1a;基于国内外数据交易市场的发展现状与平台思维视角…

用GDB调试cmake编译后的C++程序

文章目录 编译时添加参数用GDB运行程序参考 编译时添加参数 cmake -DCMAKE_BUILD_TYPEDebug <path and other arguments>用GDB运行程序 开始调试 start设置断点&#xff0c;这样会在运行到源码第6行时停止&#xff0c;可以查看变量的值、堆栈情况等&#xff1b;这个行…

71.建立一个轮播图组件第二部分

这节课我们通过绝对定位的方式来实现上一届未实现的内容&#xff01; ● 首先我们先设置一下button和图标 /* CONTROLS */.btn {background-color: #fff;border: none;height: 40px;width: 40px;}.btn-icon {height: 24px;width: 24px;}● 接着我们来设置绝对定位 .carousel {p…

如何在 FPGA 中做数学运算

FPGA 非常适合进行数学运算&#xff0c;但是需要一点技巧&#xff0c;所以我们今天就看看如何在 FPGA 中进行简单和复杂的数学运算。 介绍 由于FPGA可以对算法进行并行化&#xff0c;所以FPGA 非常适合在可编程逻辑中实现数学运算。我们可以在 FPGA 中使用数学来实现信号处理、…

Java进阶-面向对象进阶(static和继承)

1 复习回顾 1.1 如何定义类 类的定义格式如下: 修饰符 class 类名 {// 1.成员变量&#xff08;属性&#xff09;// 2.成员方法 (行为) // 3.构造方法 &#xff08;初始化类的对象数据的&#xff09; }例如: public class Student {// 1.成员变量public String name ;public…

简易axure设计原型流程

我们这里做的是移动端的项目&#xff0c;基于提供的参考视频&#xff0c;截图了app的图片&#xff0c;作为实现原型时候的背景图。 原来实现的移动端的项目是基于vant开发的&#xff0c;因而下载了vant的元件库。参考地址: Vant 4 - A lightweight, customizable Vue UI libr…

python入门(八) 书体检测和识别

文章目录 实现思路:python环境开发工具传统形态学侵蚀&#xff0c;膨胀&#xff0c;张开&#xff0c;闭合侵蚀膨胀张开闭合 canny边缘检测算法检测书法文字案例实现步骤 本人工作中&#xff0c;用到了ai相关技术&#xff0c;但是java出身&#xff0c;所以从0开始学习&#xff0…