目录
1.驱动模块(驱动程序的框架)
2.内核中的打印函数(编写第一个驱动程序)
Source Insight 使用:
打印函数编写
分析
3.驱动的多文件编译
4.模块传递参数
安装好驱动之后如何传参?
多驱动之间调用(导出符号表)
具体过程
5.字符设备驱动
字符设备驱动的注册
具体过程:
具体代码
总结归纳:
手动创建设备文件
应用程序如何将数据传递给驱动(读写的方向是站在用户的角度来说的)
具体步骤
入口(安装):资源的申请
出口(卸载):资源的释放
许可证: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
vi hello.c
make
SI3US-361500-17409
--------------------------------------打印级别--------------------------------------
#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一样,需要打印的参数
#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");
安装卸载无反应,因为ubuntu基于linux内核做的开发
PM:
dmesg (查看消息的回显) dmesg -c (查看回显并清空)dmesg -C (清空回显)
可以不写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
(修改开发板)
= 赋值 需要等待其他文件全部执行完,才执行调用的
:= 立即赋值
+= 附加赋值
?= 询问变量之前是否被赋值过 如果赋值过,本次赋值不成立,否则成立
hello.c add.c
Makefile
obj-m:=demo.o
demo-y+=hello.o add.o
最终生成demo.ko文件
vi makefile
增添完后 make
然后make clean
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");
替换Linux6818/demo的hello.c文件 然后进行修改之前的双编译Makefile 即vi Makefile
完事后 进行make
完事后 卸载驱动 sudo rmmod hello
传递两个参数程序
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,"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
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
具体过程:
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
1.lsmod查看驱动名字
2.找路径 /sys/module/驱动模块的名字/parameters
3.修改 -》su root -》echo 需要改为多少>需要修改的参数名
4.cat 需要修改的参数名(查看是否修改成功)
假如有两个驱动模块,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 /
vi add.c
make
vi hello.c
APP层 ---- 读写操作 ----→
open打开一个文件 --→{ open=hello、read=buf、write=ubuf、close=hello}
内核层 ---- 读写文件 ----→
|
v
硬件层 ---- GPIO输出 ----→ LED灯
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
#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、注销设备驱动
待加程序
#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;
}
命名空间存数据
在用户角度,应用层程序读
参数1 内核到用户空间的地址
参数2 来自内核的地址
参数3 长度
加上判断
判断是否成功返回
写是从用户到内核
进行make
cat /proc/devices 查看主设备号。/
vi test.c
do dmesg