$2$驱动模块

目录

1.驱动模块(驱动程序的框架)

2.内核中的打印函数(编写第一个驱动程序)

Source Insight 使用:

打印函数编写

分析

3.驱动的多文件编译

4.模块传递参数

安装好驱动之后如何传参?

 多驱动之间调用(导出符号表)

具体过程

5.字符设备驱动

字符设备驱动的注册

 具体过程:

具体代码

总结归纳:

手动创建设备文件

应用程序如何将数据传递给驱动(读写的方向是站在用户的角度来说的)

具体步骤



 

1.驱动模块(驱动程序的框架)

入口(安装):资源的申请

出口(卸载):资源的释放

许可证:GPL

#include 
#include 
//声明   //static 是为了防止别人的驱动和你重名 
static int __init hello_init(void)    //入口
{

}
static void __exit hello_exit(void)   //出口
{

}
//告诉内核驱动的入口
module_init(hello_init);
//告诉内核驱动的出口
module_exit(hello_exit);
MODULE_LICENSE("GPL");    //许可证

Makefile

KERNELDIR:= /lib/modules/$(shell uname -r)/build/  //ubuntu 内核目录
#KERNELDIR:= /home/linux/kernel/kernel-3.4.39/    //开发板内核目录
PWD:=$(shell pwd)
all: 
    make -C $(KERNELDIR) M=$(PWD) modules
clean:
    make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o 	

具体过程:

vi Makefile

 ls$2$驱动模块_第1张图片

 

 

 $2$驱动模块_第2张图片

 $2$驱动模块_第3张图片

 更改

 $2$驱动模块_第4张图片

 $2$驱动模块_第5张图片

 $2$驱动模块_第6张图片

 $2$驱动模块_第7张图片$2$驱动模块_第8张图片

 vi hello.c

$2$驱动模块_第9张图片

make

$2$驱动模块_第10张图片

$2$驱动模块_第11张图片

 $2$驱动模块_第12张图片

 $2$驱动模块_第13张图片

 $2$驱动模块_第14张图片

 SI3US-361500-17409

2.内核中的打印函数(编写第一个驱动程序)

Source Insight 使用:

$2$驱动模块_第15张图片

 $2$驱动模块_第16张图片

$2$驱动模块_第17张图片

 $2$驱动模块_第18张图片

 $2$驱动模块_第19张图片

 $2$驱动模块_第20张图片

$2$驱动模块_第21张图片

$2$驱动模块_第22张图片

$2$驱动模块_第23张图片

 $2$驱动模块_第24张图片

 $2$驱动模块_第25张图片

 $2$驱动模块_第26张图片

 $2$驱动模块_第27张图片

打印函数编写

分析

--------------------------------------打印级别--------------------------------------
#define KERN_EMERG	"<0>"	/* system is unusable			*/
#define KERN_ALERT	"<1>"	/* action must be taken immediately	*/
#define KERN_CRIT	"<2>"	/* critical conditions			*/
#define KERN_ERR	"<3>"	/* error conditions			*/
#define KERN_WARNING	"<4>"	/* warning conditions			*/
#define KERN_NOTICE	"<5>"	/* normal but significant condition	*/
#define KERN_INFO	"<6>"	/* informational			*/
#define KERN_DEBUG	"<7>"	/* debug-level messages			*/
y@y-virtual-machine:~/linux6818/demo$ cat /proc/sys/kernel/printk
   4              4	                 1	            7     

终端的级别   消息的默认级别   终端的最大级别   终端的最小级别  
#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
--------------------------------打印函数---------------------------------------------
printk(KERN_ERR "BFS-fs: %s(): " format, __func__, ## args)
功能:消息打印
参数:
   第一个参数:打印的级别
   第二个参数:打印的内容
   第三个参数:和printf一样,需要打印的参数

 ubuntu级别

#include 
#include 
#include 
static int __init hello_init(void)//入口
{
  printk(KERN_ERR "hello word\n");
  return 0;
}
static void __exit hello_exit(void)//出口
{
  printk(KERN_ERR "baibai\n");
}
module_init(hello_init);//告诉内核驱动的入口
module_exit(hello_exit);//告诉内核驱动的出口
MODULE_LICENSE("GPL");

$2$驱动模块_第28张图片

 安装卸载无反应,因为ubuntu基于linux内核做的开发

PM:

 $2$驱动模块_第29张图片

dmesg (查看消息的回显) dmesg -c (查看回显并清空)dmesg -C (清空回显)

 $2$驱动模块_第30张图片

 $2$驱动模块_第31张图片

 可以不写KERN _ERR 以默认的级别显示

重点:

ctrl+Alt+F5 (进入虚拟控制台)ctrl+Alt+F7(退出虚拟控制台)-》有的退出是F2
sudo insmod  hello.ko(安装驱动)sudo rmmod  hello(卸载驱动)
 dmesg (查看消息的回显)  dmesg -c  (查看回显并清空)dmesg -C  (清空回显)
cat /prod/sys/kernel/printk 
(查看ubuntu终端显示级别 消息默认级别 消息最大级别  消息最小级别)
su root echo 4 3 1 7 > /proc/sys/kernel/printk
(修改ubuntu终端显示级别 消息默认级别 消息最大级别  消息最小级别)
echo 4 3 1 7 > /proc/sys/kernel/printk
(修改开发板)
= 赋值 需要等待其他文件全部执行完,才执行调用的 
:= 立即赋值
+= 附加赋值
?= 询问变量之前是否被赋值过 如果赋值过,本次赋值不成立,否则成立

3.驱动的多文件编译

hello.c  add.c
	 Makefile
	 obj-m:=demo.o
	 demo-y+=hello.o add.o
	最终生成demo.ko文件	

$2$驱动模块_第32张图片

$2$驱动模块_第33张图片

 vi makefile

$2$驱动模块_第34张图片

$2$驱动模块_第35张图片

增添完后 make

然后make clean

4.模块传递参数

module_param(name, type, perm)    //驱动安装时候的安装路径,或者安装时配置的参数

功能:接收命令行传递的参数

参数:

name: 变量的名字  type:变量的类型  perm:权限 0664 0775 
#include 
#include 
int a=10;
module_param(a,int,0664);                   
static int __init hello_init(void)
{
 printk("sum= %d\n",a);
 return 0;
}
static void __exit hello_exit(void)
{
 printk(KERN_ERR"bai");                
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
$2$驱动模块_第36张图片

 替换Linux6818/demo的hello.c文件 然后进行修改之前的双编译Makefile 即vi Makefile

$2$驱动模块_第37张图片

 完事后 进行make

$2$驱动模块_第38张图片

 $2$驱动模块_第39张图片

 

 完事后 卸载驱动 sudo rmmod hello

$2$驱动模块_第40张图片

传递两个参数程序

int a=10;
module_param(a,int,0664);    
int b=10;
module_param(b,int,0664);    
$2$驱动模块_第41张图片

$2$驱动模块_第42张图片

问:安装时怎么区分a和b的作用,自己写的知道那如果移植其他厂商的呢比如LCD屏

使用modinfo hello.ko查看,且需要程序里使用如下函数进行配置

$2$驱动模块_第43张图片

 无法看出哪个是什么功能 无法提示

MODULE_PARM_DESC(_parm, desc)
功能:对变量的功能进行描述
参数:
	@_parm:变量
	@desc :描述字段
int a=10;
module_param(a,int,0664);
MODULE_PARM_DESC(a,"light");
short b=123;
module_param(b,short,0664);
MODULE_PARM_DESC(b,"clour");
char c='c';
 module_param(c,byte,0664);
 MODULE_PARM_DESC(c,"light_c");
 char *d=null;
 module_param(d,charp,0664);
  MODULE_PARM_DESC(d,"clour");
 static int __init hello_init(void)
{ 
    printk("sum= %d\n",a);
    printk("sum= %d\n",b);
    printk("sum= %c\n",c);
    printk("sum= %s\n",d);                                                                                                                                                                                 
return 0;
}
static void __exit hello_exit(void)
 {
  printk(KERN_ERR"bai");
}
 module_init(hello_init);
 module_exit(hello_exit);
 MODULE_LICENSE("GPL");

vi hello.c

$2$驱动模块_第44张图片

 $2$驱动模块_第45张图片

 $2$驱动模块_第46张图片

int a=10;

int b=0;

module_param(a, int, 0664) ;

module_param(b, int, 0664) ;

MODULE_PARM_dESC(a,"light");

MODULE_PARM_dESC(b,"color");

sudo insmod hello.ko a=20

make

modinfo hello.ko

sudo insomd hello.ko a=30 b=24

sudo dmesg -c

数组传参:

module_param_array(name,type,nump,perm)
功能:接收命令行传递的数组
参数:
name:数组名 type :数组类型  nump:参数的个数,变量的地址 perm 权限
int ww[10]={0}; 
int num;
module_param_array(ww,int,&num,0664)
static int __init hello_init(void)
{
    int i;
    for(i=0;i

sudo insmod hello.ko ww=1,2,3,4,5

具体过程:

$2$驱动模块_第47张图片

 $2$驱动模块_第48张图片

 $2$驱动模块_第49张图片

 $2$驱动模块_第50张图片

 $2$驱动模块_第51张图片

int ww[10]={0}; 
int num;
module_param_array(ww,int,&num,0664)
static int __init hello_init(void)
{
    int i;
    for(i=0;i

make

sudo insmod hello.ko ww=1,2,3,4,5

$2$驱动模块_第52张图片

安装好驱动之后如何传参?

1.lsmod查看驱动名字

2.找路径 /sys/module/驱动模块的名字/parameters

3.修改 -》su root -》echo 需要改为多少>需要修改的参数名

4.cat 需要修改的参数名(查看是否修改成功)

$2$驱动模块_第53张图片

 多驱动之间调用(导出符号表)

假如有两个驱动模块,modul1和modu2 。这两个是可以调用的

cp  ../day1/module  .  -a    拷贝module到当前目录下

makdir export   mv module/  export/ 

mv module/   A 重命名moudule为A

cp A B -a  拷贝一份并命名为B

#include 
#include 
int add(int a,int b)
{
 return (a+b);
}
EXPORT_SYMBOL_GPL(add);//导出符号表
static int __init hello_init(void)
{
 return 0;
}
staic int __exit hello_exit(void)
{
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

进入B,mv hello.c demo.c改下名字

然后vi打开

extern int add(int a,int b);
static int __init hello_init(void)
{
    printk("sum = %d\n",add(10,345));
}

编译

  make  

 发现报错,提示add没有定义

 所以在执行之前把A里面大M开头文件复制到当前目录下   

mv  cp  ../A/Module.symvers

然后再make

安装:

 先安装提供者 sudo insmod hello.ko

 再安装调用者 sudo insmod  demo.ko

查看信息:dmesg

卸载:

先卸载 demo.ko  再卸载 hello.ko

具体过程

cp -r /

$2$驱动模块_第54张图片

 $2$驱动模块_第55张图片

 $2$驱动模块_第56张图片

vi add.c

 $2$驱动模块_第57张图片

 make

$2$驱动模块_第58张图片

 $2$驱动模块_第59张图片

 vi hello.c

$2$驱动模块_第60张图片

 $2$驱动模块_第61张图片

5.字符设备驱动

APP层    ---- 读写操作 ----→

                                     open打开一个文件 --→{ open=hello、read=buf、write=ubuf、close=hello}

内核层    ---- 读写文件 ----→

   |

   v

硬件层    ---- GPIO输出 ----→     LED灯    

$2$驱动模块_第62张图片

字符设备驱动的注册

   int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

功能:注册一个字符设备驱动

参数:

@major:主设备号  

  :如果你填写的值大于0,它认为这个就是主设备号

  :如果你填写的值为0,操作系统给你分配一个主设备号   

@name :名字    cat /proc/devices 查看设备名和主设备号

@fops :操作方法结构体

返回值:major>0 ,成功返回0,失败返回错误码(负数) (vi -t EIO 可以查看错误码)

major=0,成功主设备号,失败返回错误码(负数)      

void unregister_chrdev(unsigned int major, const char *name)

功能:注销一个字符设备驱动

参数:

@major:主设备号

@name:名字

返回值:无

 具体过程:

$2$驱动模块_第63张图片

 开始注册,起一个变量

$2$驱动模块_第64张图片

 $2$驱动模块_第65张图片

 $2$驱动模块_第66张图片

 $2$驱动模块_第67张图片

具体代码

#include 
#include 
#include 
#include 
int major=0;
#define CNAME "hello"
ssize_t mycdev_read (struct file *file, char __user *user, size_t size, loff_t * loff)
{
   printk("this is read");
   return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *user, size_t size, loff_t *loff)
{
   printk("this is write");
   return 0;
}
int mycdev_open (struct inode *inode, struct file *file)
{
   printk("this is open");
   return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
   printk("this is close");
   return 0;
}
	
const struct file_operations fops={
   .open=mycdev_open,
   .read=mycdev_read,
   .write=mycdev_write,
   .release=mycdev_release,
};

static int __init hello_init(void)//入口
{
  major=register_chrdev(major,CNAME,&fops);
  if(major<0)
  {
   printk("register chrdev error");
  }
  return 0;
}
static void __exit hello_exit(void)//出口
{
  unregister_chrdev(major,CNAME);
}
module_init(hello_init);//告诉内核驱动的入口
module_exit(hello_exit);//告诉内核驱动的出口
MODULE_LICENSE("GPL");

总结归纳:

字符设备驱动:

 1、注册驱动register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

major=0  自动分配设备号  name:驱动的名字   fops:结构体

 2、声明结构体-》注册驱动时第三个参数

 3、open、read、write、release-》按照内核的格式自己写的

 4、把自己写的这些函数->给到结构体里-》.open .read .write .release

 5、注销设备驱动

手动创建设备文件

安装 sudo insmod hello.ko

cat /proc/devices 查看主设备号

sudo  mknod hello(路径:是任意的)   c/b (C代表字符设备 b代表块设备) 主设备号   次设备号

通过cat 查看hello的主设备号,列如为238

$2$驱动模块_第68张图片

 $2$驱动模块_第69张图片

 

 

$2$驱动模块_第70张图片

 

 $2$驱动模块_第71张图片$2$驱动模块_第72张图片

待加程序

应用程序如何将数据传递给驱动(读写的方向是站在用户的角度来说的)

#include 
	int copy_from_user(void *to, const void __user *from, int n)
	功能:从用户空间拷贝数据到内核空间
	参数:
		@to  :内核中内存的首地址
		@from:用户空间的首地址
		@n   :拷贝数据的长度(字节)
	返回值:成功返回0,失败返回未拷贝的字节的个数
	int copy_to_user(void __user *to, const void *from, int n)
	功能:从内核空间拷贝数据到用户空间
	参数:
		@to  :用户空间内存的首地址
		@from:内核空间的首地址  __user需要加作用是告诉编译器这是用户空间地址
		@n   :拷贝数据的长度(字节)
	返回值:成功返回0,失败返回未拷贝的字节的个数
#include 
#include 
#include 
#include 
#define CNAME "hello"
int major=0;
int dev;
char  kbuf[128]={0};
int mycdev_open (struct inode *inode, struct file *file)
{
  printk("open");
  return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf,
	size_t size, loff_t *offs)
{
  printk("read");
  if(size>sizeof(kbuf)) size=sizeof(kbuf);
  dev=copy_to_user(ubuf,kbuf,size);
  if(dev)
  	{
	  printk("copy to user errer");
	  return -EINVAL;
    }
  return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, 
	size_t size, loff_t *offs)
{
  if(size > sizeof(kbuf)) size=sizeof(kbuf);
  dev=copy_from_user(kbuf,ubuf,size);
  if(dev)
  {
    printk("copy from user errer");
	return -EINVAL;
 }
  printk("write");
  return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
  printk("close");
  return 0;
}
const struct file_operations fops={
	.open=mycdev_open,
	.write=mycdev_write,
    .read=mycdev_read,
	.release=mycdev_release,
};

static int __init mycdev_init(void)
{
  major=register_chrdev(major,CNAME,&fops);
  if(major<0)
  	{
 		printk("register char device error\n");
		return major;
  	}
  return 0;
}
static void __exit mycdev_exit(void)
{
  unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL")

应用程序

#include 
#include 
#include 
#include 
#include 
#include 
char buf[128]="this is mycdev";
int main(int argc, const char *argv[])
{
	int fd;
	fd=open("./hello",O_RDWR);
	if(fd==-1)
	{
		perror("open error");
		return -1;
	}
	write(fd,buf,sizeof(buf));
	memset(buf,0,sizeof(buf));
	read(fd,buf,sizeof(buf));
	printf("buf is :%s\n",buf);
	close(fd);
	return 0;
}

具体步骤

命名空间存数据

$2$驱动模块_第73张图片

 在用户角度,应用层程序读

$2$驱动模块_第74张图片

 参数1 内核到用户空间的地址

参数2 来自内核的地址

参数3 长度

$2$驱动模块_第75张图片

 加上判断

$2$驱动模块_第76张图片

 判断是否成功返回

$2$驱动模块_第77张图片

 

 写是从用户到内核

$2$驱动模块_第78张图片

 $2$驱动模块_第79张图片$2$驱动模块_第80张图片

 

 进行make

cat /proc/devices 查看主设备号。/

$2$驱动模块_第81张图片

 vi test.c

 $2$驱动模块_第82张图片

 

 $2$驱动模块_第83张图片

 $2$驱动模块_第84张图片

 do dmesg

$2$驱动模块_第85张图片

 

 

 

你可能感兴趣的:(单片机,嵌入式硬件,linux)