记录学习Linux驱动开发中遇到的一系列问题
Linux常用指令
记录一些在学习时常用的指令
一些特别常用的就不记录啦
gedit 文件名//修改文件
- 卸载驱动,如果想卸载当前未被使用的模块,可以使用
lsmod
命令列出所有当前加载的模块,然后用rmmod 驱动名
卸载。 cp -rf
,用于递归地复制文件和目录,并且在复制过程中会覆盖已存在的目标文件。
Linux驱动开发
一.第一个实验(hello)
1.代码
①.hello_drv.c
#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/uaccess.h>
#include <linux/module.h>
static int major;
static int hello_open(struct inode *node, struct file *filp){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏,横线为两个横线
return 0;
};
static ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return size;
};
static ssize_t hello_write(struct file *flip, const char __user *buf, size_t size, loff_t *offset){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return size;
};
static int hello_release (struct inode *node, struct file *filp){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return 0;
};
//1.构造operations结构体
static const struct file_operations hello_drv = {
.owner=THIS_MODULE,
.read = hello_read,**//.read其实是函数指针,右面应该是取地址,&hello_read,但是函数可以省略取地址**
.write = hello_write,
.open = hello_open,
.release = hello_release,
};
//2.resister_ chrdev
//3.入口
static int hello_init(void)
{
major=register_chrdev(0,"hello",&hello_drv);
return 0;
}
//4.出口
static void hello_exit(void)
{
unregister_chrdev(major,"hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
②.hello_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int fd;
int len;
char buf[100];
int main(int argc,char**argv){
if(argc<2){
printf("Usage:\n");
printf("%s <dev> [string]\n",argv[0]); //<>表示不能省略 []表示可以省略
return -1;
}
//open
fd=open(argv[1],O_RDWR);
if(fd<0){
printf("cannot open file %s\n",argv[0]);
return -1;
}
//write
if(argc==3){
len=write(fd,argv[2],strlen(argv[2]+1));//write函数write(fd,buf,len),
/*注意strlen(argv[2]+1),+ 1 是为了包含字符串末尾的空字符(null character)
C语言中的字符串是以空字符(\0)作为字符串的结束标志的。strlen函数会计算字符串中除了
空字符之外的字符数量。当你使用write函数将字符串写入文件时,通常需要包含空字符,以确
保完整地写入整个字符串,包括字符串的结束标志。因此,strlen(argv[2]) + 1 用于计算
存储在 argv[2] 中的字符串的长度,并添加额外的 1 个字节来包含空字符。这样,write函
数将会写入整个字符串,包括空字符,确保数据的完整性。*/
printf("write ret=%d\n",len);
}
else{
//read
len=read(fd,buf,100);//read函数write(fd,buf,len)
buf[99]='\0';
printf("read str:%s\n",buf);
}
//close
close(fd);
return 0;
}
③.makefile
#1. 使用不同的开发板内核时, 一定要修改KERN_DIR
#2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
#2.1 ARCH, 比如: export ARCH=arm64
#2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
#2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
#注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
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
makefile写完后保存并make
4.执行步骤
1.连接开发板(有一次串口连接没连上,搜不到串口,后重启才连上)。
2.挂载网络系统
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
3.有些板子把内核信息屏蔽了,打开内核信息,echo "7 4 1 7" > /proc/sys/kernel/printk
4.有的内核太老了,需要更新板子内核(不详细解释)
5.在开发板中进入/mnt/(后面是挂载的文件夹)
6.输入insmod hello_drv.ko
7.装载成功后,输入cat /proc/devices
查看所有设备,记录新添加的hello主设备号,本例为240
8.可以改名(存疑)mknod /dev/xyz c 240 0
xyz表示名字不重要,c表示是字符型设备,240为主设备号,0为次设备号
9.进行测试
./hello_test /dev/xyz
./hello_test /dev/xyz 123
可以看到不输入123时只进行读操作,输入123时就开始写操作,是由argc==3判断的
5.注意事项
bear make
//bear命令用来生成compile_commands.json,它会记录make过程编译文件时用到的命令。如果之前曾经编译过内核,要用make clearn
清除掉,然后bear make zImage -j 4
重新编译,编译成功后就会在当前目录下得到文件compile_commands.json。然后将里面的cc全部替换为arm-buildroot-linux-gnueabihf-gcc。在这个文档里可以看到引用的头文件。
二.hello驱动程序实验改进
1.基础知识
①APP和驱动的交互方式
- app和驱动之间通过
copy_to _user
和copy_from_user
传递数据 - 驱动程序和硬件之间可以通过使用原始函数或者直接通过
ioremap
去映射寄存器地址后,直接访问寄存器
②APP使用驱动的四种方式
驱动程序:提供能力,不提供策略
- 非阻塞(查询):不会等待,有就是有没有就是没有,该实验代码就是非阻塞
- 阻塞(休眠-唤醒):等待有数据
- poll(定个闹钟):唤醒条件a时间到;b发生了中断
- 异步通知
③中断
2.修改代码
①读取和存入数据
static unsigned char hello_buf[100];//先定义一个内核空间
static ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset){
unsigned long len=size>100?100:size;
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
copy_to_user(buf,hello_buf,len);//从内核空间读到用户空间
return len;
};
static ssize_t hello_write(struct file *flip, const char __user *buf, size_t size, loff_t *offset){
copy_from_user(hello_buf,buf,len);//从用户空间写到内核空间
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return len;
};
②class和device讲解
先定义一个全局变量
static struct class *hello_class;
再进行创建,记得销毁
static int hello_init(void)
{
major=register_chrdev(0,"hello",&hello_drv);
hello_class=class_create(THIS_MODULE,"hellonihao");
if(IS_ERR(hello_class)){
printk("failed\n");
return PTR_ERR(hello_class);
}
device_create(hello_class,NULL,MKDEV(major,0),NULL,"hello");//创建一个设备节点,名字为hello,与类的名字没有关系
return 0;
}
static void hello_exit(void)
{
device_destroy(hello_class,MKDEV(major,0));
class_destroy(hello_class);//创建完还要销毁
unregister_chrdev(major,"hello");
}
class和device这两个函数,实际上是在sys文件架下创建了虚拟节点
240和0为主设备号和次设备号,系统后台程序根据信息生成设备节点,即生成/dev/hello
3.完整代码
①hello_drv.c
#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/uaccess.h>
#include <linux/module.h>
static int major;
static unsigned char hello_buf[100];
static struct class *hello_class;
static int hello_open(struct inode *node, struct file *filp){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return 0;
};
static ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *offset){
unsigned long len=size>100?100:size;
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏copy_to_user(buf,hello_buf,len);
copy_to_user(buf,hello_buf,len);
return len;
};
static ssize_t hello_write(struct file *flip, const char __user *buf, size_t size, loff_t *offset){
unsigned long len=size>100?100:size;
copy_from_user(hello_buf,buf,len);
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return len;
};
static int hello_release (struct inode *node, struct file *filp){
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);//使用宏
return 0;
};
//1.构造operations结构体
static const struct file_operations hello_drv = {
.owner=THIS_MODULE,
.read = hello_read,
.write = hello_write,
.open = hello_open,
.release = hello_release,
};
//2.resister_ chrdev
//3.入口
static int hello_init(void)
{
major=register_chrdev(0,"hello",&hello_drv);
hello_class=class_create(THIS_MODULE,"hello");
if(IS_ERR(hello_class)){
printk("failed\n");
return PTR_ERR(hello_class);
}
device_create(hello_class,NULL,MKDEV(major,0),NULL,"hello");
return 0;
}
//4.出口
static void hello_exit(void)
{
device_destroy(hello_class,MKDEV(major,0));
class_destroy(hello_class);
unregister_chrdev(major,"hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
②hello_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int fd;
int len;
char buf[100];
int main(int argc,char**argv){
if(argc<2){
printf("Usage:\n");
printf("%s <dev> [string]\n",argv[0]); //<>biaoshibunengshenglue []biaoshikeyishenglue
return -1;
}
//open
fd=open(argv[1],O_RDWR);
if(fd<0){
printf("cannot open file %s\n",argv[0]);
return -1;
}
//write
if(argc==3){
len=write(fd,argv[2],strlen(argv[2])+1);
printf("write ret=%d\n",len);
}
else{
//read
len=read(fd,buf,100);
buf[99]='\0';
printf("read str:%s\n",buf);
}
//close
close(fd);
return 0;
}
makefile不变
4.最终结果
三.字符设备的另一种注册方式
按上述注册的话,一次注册一个主设备号,最多只有255个,一个设备霸占一个主设备号的所有次设备号,所以可以改进,但是没有很大的必要。一般还是使用register_chrdev
。
先申请一个主次设备号的空间,然后初始化cdev,与file_operations挂钩,然后再添加cdev
static struct cdev hello_cdev;
static dev_t dev;//先定义一个结构体
static int hello_init(void)
{
int ret;
ret = alloc_chrdev_region(&dev, 0, 1, "hello");//申请一个区域。次设备号从0开始,获得一个次设备号
cdev_init(&hello_cdev,&hello_drv);
ret=cdev_add(&hello_cdev,dev,1);
if(ret){
return -1;
}
if (ret < 0) {
printk(KERN_ERR "alloc_chrdev_region() failed for hello\n");
return -1;
}
hello_class=class_create(THIS_MODULE,"hello");
if(IS_ERR(hello_class)){
printk("failed\n");
return PTR_ERR(hello_class);
}
device_create(hello_class,NULL,dev,NULL,"hello");
return 0;
}
//出口也要删除掉cdev
static void hello_exit(void)
{
device_destroy(hello_class,dev);
class_destroy(hello_class);
cdev_del(&hello_cdev);
unregister_chrdev_region(dev,1);//释放申请的区域,一个
}
代码注意修改部分,MKDEV(major,0)
改为了dev
,一次只申请了一个,如果选哟申请多个子设备号,需将1改为2。