1.简单字符驱动
字符设备驱动:因为软件操作设备是是以字节为单位进行的,是按照字节流进行读写操作的一种设备。典型的如LCD、蜂鸣器、SPI、触摸屏等驱动,都属于字符设备驱动的范畴。大部分的驱动程序都是属于字符设备驱动。
2.设备驱动程序功能
应用程序位于用户空间,驱动程序位于内核空间。在Linux中用户空间不能直接调用内核空间的函数,所以必须要经过系统调用,应用程序才可以调用驱动程序的函数。
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,运行在核心态,它完成以下的功能:
对设备初始化和释放;
把数据从内核传送到硬件和从硬件读取数据;
读取应用程序传送给设备文件的数据和回送应用程序请求的数据;
检测和处理设备出现的错误;
————————————————
版权声明:本文为CSDN博主「嵌入式悦翔园」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45172832/article/details/125534071
3.第一个操作源码helloworld
1、头文件
初始化头文件
#include <linux/init.h>
初始化加载模块头文件
#include <linux/module.h>
2、驱动的入口和出口
module_init();
module_exit();
3、开源信息声明和作者信息
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
4、驱动基础框架
static int __init helloworld_init(void){
return 0;
}
static void __exit helloworld_exit(void){
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");
5、先进入到你自己创建的文件夹中例如test/kernel/helloworld
6、安装环境依赖
sudo apt-get install build-essential linux-headers-$(uname -r)
build-essential Ubuntu系统中预安装的软件包,包含了常用的编译工具,例如gcc、g++、make、dpkg-dev等,可用于编译和构建软件
查看当前内核版本号
uname -r
linux-headers-$(uname -r):Ubuntu系统中用于安装当前内核版本头文件的软件包。头文件包含了内核的函数原型、宏定义、结构体定义等信息,可以用于编译内核模块和驱动程序。
按照这四步骤就可以搭建一个简单的基础框架了
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
static int __init helloworld_init(void){
printk("helloworld_init\n");
return 0;
}
static void __exit helloworld_exit(void){
printk("helloworld_exit\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");
_init:该关键字标记表示该函数只会在模块初始化期间被调用,而不会在模块运行期间被调用。在驱动程序中,helloworld_init()函数被标记为_init.
_exit:该关键字可以用于标记函数只会在模块退出期间被调用,而不会在模块运行期间被调用。在驱动程序中,helloworld_exit()函数被标记为_exit.
module_init:该宏用于指定模块初始化函数,helloworld_int()函数被指定为驱动初始化函数。
module_exit:该宏用于指定模块退出函数,helloworld_exit()函数被指定为驱动退出函数。
7、编写Makefile文件
描述helloworld.c编译成一个独立的lo文件源文件
在vscode中创建一个Makefile文件写入//obj-m +=helloworld.o;
# atemsys.ko: Provides usermode access to:
#
# - PCI configuration space
# - Device IO memory
# - Contiguous DMA memory
# - Single device interrupt
#
# Copyright (c) 2009 - 2018 acontis technologies GmbH, Ravensburg, Germany <info@acontis.com>
# All rights reserved.
#
# Author: K. Olbrich <k.olbrich@acontis.com>
#
# To compile and load the atemsys driver
#
# make modules
# [ -c /dev/atemsys ] || sudo mknod /dev/atemsys c 101 0
# sudo insmod atemsys.ko
CONFIG_MODULE_SIG=n
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += helloworld.o
all: modules
modules:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules_install
clean:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules clean
obj-[x]:用于描述需要编译的目标的属性
obj-y:表示将指定对象编译为内核的一部分(即静态链接),这意味着该对象将包含在内核映像中,无法卸载或修改。通常情况下,obj-y用于编译内核的核心组件,例如进程调度器、文件系统、网络协议栈等。
obj-m:表示将指定对象编译为可加载模块(即动态链接),这意味着该对象为单独的模块编译,并在需要时加载到内核中。通常情况下,obj-m用于编译内核的外围组件,例如设备驱动程序、文件系统模块、网络协议栈模块等。
obj-$(CONFIG_YOUR_DEF):同时也可是一个变量。例如放在Kcongfig中共用户配置,本质还是obj-y和obj-m
8、编译成KO文件
编译当前目录的内核模块,生成对应的编译产物(最重要的是ko驱动文件)
在终端中
make -C /lib/modules/$(uname -r)/build M=`pwd` modules
可以用cd进/lib/modules/$(uname -r)/build中用ls查看内核的一个构建目录,查看完记得退回自己创建的文件夹中
-C:选项用于指定工作目录(或Makefile文件的位置),让命令在指定的目录下执行,而不是在当前目录下执行。
-M:选项用于指定模块源代码目录,让命令在指定的目录下查找模块源代码(pwd获取当前目录路径)
modules:参数用于指定要编译的目标是模块文件。Linux内核编译中,make命令可以使用modules参数来指定只编译模块文件,而不编译整个内核。
9、安装/查看/卸载驱动
驱动的安装卸载及对应的内核log查看
insmod命令安装内核模块
sudo insmod helloworld.ko
lsmod查看当前运行的内核模块
#用grep过滤
lsmod | grep -Ei helloworld
rmmod卸载内核模块
sudo rmmod helloworld
dmesg查看内核日志
#log 太多 用grep 过滤一下
dmesg | grep -Ei helloworld
4.简单线程调用
1、什么时线程
在进行进程切换时,需要不断刷新cache(缓存),为了减少cahce刷新时的资源消耗,我们引入了轻量级进程——线程。
进程称为最小的资源分配单位,线程称为CPU最小的任务调度单位。
2、线程的特点
同意个进程创建的多个线程,它们共用同一个进程的地址空间。
进程创建线程后,我们把原来进程也称为线程,并且为主线程。
3、函数例子
1)Helloworld驱动文件
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/poll.h>
#include<linux/proc_fs.h>
#include<linux/skbuff.h>
#include<linux/seq_file.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/uaccess.h>
static int major = 200;
struct device *class_dev = NULL;
struct class *cls;
static int helloworld_open(struct inode* inode,struct file* file)
{
printk("helloworld_open()\n");
return 0;
}
static int helloworld_release(struct inode* inode,struct file* file)
{
printk("helloworld_release()\n");
return 0;
}
#define KMAX_LEN 32
char kbuf[KMAX_LEN] = "kernel";
static ssize_t helloworld_read(struct file *filep,char __user* buf,size_t size,loff_t* pos)
{
int error;
if(size > strlen(kbuf))
{
size = strlen(kbuf);
}
if(copy_to_user(buf,kbuf,size))
{
error = -EFAULT;
return error;
}
return size;
}
static ssize_t helloworld_write(struct file* filep,const char __user* buf,size_t size,loff_t* pos)
{
int error;
if(size > KMAX_LEN)
{
size = KMAX_LEN;
}
memset(kbuf,0,sizeof(kbuf));
if(copy_from_user(kbuf,buf,size))
{
error = -EFAULT;
return error;
}
printk("%s\n",kbuf);
return size;
}
static struct file_operations helloworld_ops={
.owner=THIS_MODULE,
.open=helloworld_open,
.read=helloworld_read,
.write=helloworld_write,
.release=helloworld_release,
};
static int __init helloworld_init(void){
int ret;
printk("helloworld_init()\n");
ret = register_chrdev(major,"huimu",&helloworld_ops);
if(ret<0)
{
printk("register_chrdev fail\n");
}
return 0;
}
static void __exit helloworld_exit(void){
printk("helloworld_exit()\n");
unregister_chrdev(major,"huimu");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR("muhui");
MODULE_LICENSE("GPL");
进入文件目录ko文件
make -C /lib/modules/$(uname -r)/build M=`pwd` modules
一般来说直接make就可以了
2)应用层test.c文件
include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd;
int len;
char buf[64] ={0};
char buff[64] ="huimu";
fd = open("/dev/test",O_RDWR);
if(fd<0)
{
perror("open fail\n");
return 0;
}
len =read(fd,buf,64);
buf[len]='\0';
printf("read:%s len = %d\n",buf,len);
strcpy(buf,"huimu");
len = write(fd,buff,strlen(buff));
printf("len = %d\n",len);
printf("open ok\n");
close(fd);
return 0;
}
3)Makefile文件
# atemsys.ko: Provides usermode access to:
#
# - PCI configuration space
# - Device IO memory
# - Contiguous DMA memory
# - Single device interrupt
#
# Copyright (c) 2009 - 2018 acontis technologies GmbH, Ravensburg, Germany <info@acontis.com>
# All rights reserved.
#
# Author: K. Olbrich <k.olbrich@acontis.com>
#
# To compile and load the atemsys driver
#
# make modules
# [ -c /dev/atemsys ] || sudo mknod /dev/atemsys c 101 0
# sudo insmod atemsys.ko
CONFIG_MODULE_SIG=n
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += helloworld.o
all: modules
modules:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules_install
clean:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules clean
4)编译模块
make -C /lib/modules/$(uname -r)/build M=`pwd` modules
5)加载模块
sudo insmod 文件名.ko(例如helloworld.ko)
lsmod查看当前运行的内核模块
#用grep过滤
lsmod | grep -Ei helloworld
6)创建设备节点文件
1、输入 sudo mknod /dev/test(创建的文件名) c 200(你自己写的节点数) 0
如果已经存在的话sudo rm /dev/test(文件名) 删除设备节点文件然后再创建2、可以用ls /dev/test(文件名) -l这个命令来查看是否生成这个文件
vim /proc/devices可以进去查看200是否写入3、创建测试文件touch test.c并编写测试内容
编译测试文件gcc test.c或者gcc.test.c -o run
进入管理者模式sudo su
dmesg -c清除
./a.out或者./run运行,查看读取
dmesg查看open和close查看写入
4、rmmod卸载内核模块
sudo rmmod helloworld
dmesg查看内核日志
#log 太多 用grep 过滤一下
dmesg | grep -Ei helloworld