驱动开发--字符设备驱动

本文介绍了Linux驱动的基本概念和开发流程,包括驱动模块的安装与卸载,内核中的打印函数使用,以及如何编写和编译多文件驱动。同时讲解了如何传递模块参数,以及字符设备驱动的注册和注销。此外,还提到了SourceInsight在驱动开发中的应用。
摘要由CSDN通过智能技术生成

目录

1.驱动模块 

hello.c

Makefile

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

Source Insight 使用:

3.打印函数编写

分析

4、驱动的多文件编译

5、模块传递参数

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

 7、字符设备驱动

8、字符设备驱动的注册

9、总结归纳:


1.驱动模块 

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

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

许可证:GPL

hello.c

//声明函数

static int  __init hello_init(void) //入口,static :只能在当前程序使用,防止其他驱动重名

{
   return 0;
}

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

}
module_init(hello_init);//告诉内核驱动的入口

module_exit(hello_exit);//告诉内核驱动的出口

MODULE_LICENSE("GPL");

Makefile

                     ==/lib/modules/4.15.0-142-generic/build

KERNELDIR:= /lib/modules/$(shell uname -r)/build/              Ubuntu内核目录

#KERNELDIR:= /home/hq/fs6818_uboot/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 

 编写完后执行make编译,sudo insmod hello.ko安装

$make

$sudo insmod hello.ko

$sudo rmmod hello  //卸载

 

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

Source Insight 使用:

1)打开Source Insight,新建项目 Project-->New Project

2)添加自己的工程名与存放路径 ,点击OK

3)选择解压后的内核代码,点击Add All全部添加 

 

3.打印函数编写

分析

--------------------------------------------------------打印级别-----------------------------------------------------

#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 */

       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一样,需要打印的参数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
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");

没有打印信息,解决方法如下: 

PM:

方法一:虚拟控制台

       ctrl+alt+F5 或者ctrl+Fn+alt+F5(F1-F5)(进入虚拟控制台)

       ctrl+alt+F7或者ctrl+Fn+alt+F7(退出虚拟控制台)-》有的退出是F2

       sudo insmod  hello.ko(安装驱动)sudo rmmod  hello(卸载驱动)

方法二:在终端输入

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

 cat /proc/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 (修改开发板)

       =     赋值 需要等待其他文件全部执行完,才执行调用的

      :=   立即赋值

      +=   附加赋值

      ?= 询问变量之前是否被赋值过,如果赋值过本次赋值不成立,否则成立

4、驱动的多文件编译

hello . c   add . c

 Makefile

 obj-m:=demo.o

 demo-y+=hello.add.o

最终生成demo.ko文件

5、模块传递参数

module_param(name, type, perm) 

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

参数:

name: 变量的名字  type:变量的类型  perm:权限 0664 0775 

name: 变量的名字  type:变量的类型  perm:权限 0664 0775 

#include <linux/init.h>
#include <linux/module.h>
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");

 

传递两个参数程序

int  a = 10 ;

module_param(a,int,0664);

int b=10;

module_param(b,int,0664);

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

使用modinfo hello.ko查看

且需要程序里使用如下函数进行配置:

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,"color");
char c='c';
module_param(c,byte,0664);
MODULE_PARM_DESC(c,"light_c");
 static int __init hello_init(void)
{ 
    printk("sum= %d\n",a);
    printk("sum= %d\n",b);
    printk("sum= %c\n",c);                                                                                                                                                                                
return 0;
}
static void __exit hello_exit(void)
 {
  printk(KERN_ERR"bai");
}
 module_init(hello_init);
 module_exit(hello_exit);
 MODULE_LICENSE("GPL");

练习:其他类型

数组传参:

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<num;i++)
    {
     printk("ww[%d]=%d\n",i,ww[i]);
    }
}

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

 

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

  1. lsmod查看驱动名字

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

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

4、cat 需要修改的参数名  (查看是否修改成功)多驱动之间调用(导出符号表)

假如有两个驱动模块,demo和demo2 ,假设demo调用demo2中的函数

cp  -r  demo/  demo2/    先拷贝demo到demo2

mv hello.c  add.c   将demo2中hello.c重命名为add.c

在被调用者add.c中添加代码:

EXPORT_SYMBOL_GPL(add);//导出符号表 

#include <linux/intt.h>
#include <linux/module.h>
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");

执行 $make 生成Module.symvers,将其cp到demo调用者中

$vi hello.c打开调用者,添加外部引用。

extern int add ( int  a , int  b );

static int __init hello_init(void)

{

printk("sum = %d\n",add(2,3));

}

编译: make         发现报错,提示add没有定义

 所以在执行之前把add.c里面M开头文件复制到demo下   

cp  Module.symvers  ../demo             然后再make

安装: 先安装提供者 sudo insmod add.ko

            再安装调用者 sudo insmod  hello.ko

查看信息:dmesg

卸载:先卸载 调用者hello.ko  再卸载提供者add.ko

 7、字符设备驱动

8、字符设备驱动的注册  

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:名字

返回值:无

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
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, 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");

9、总结归纳:

字符设备驱动:

 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、注销设备驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值