Linux驱动开发学习记录01

记录学习Linux驱动开发中遇到的一系列问题

Linux常用指令

记录一些在学习时常用的指令
一些特别常用的就不记录啦

  1. gedit 文件名//修改文件
  2. 卸载驱动,如果想卸载当前未被使用的模块,可以使用lsmod命令列出所有当前加载的模块,然后用rmmod 驱动名卸载。
  3. 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 0xyz表示名字不重要,c表示是字符型设备,240为主设备号,0为次设备号
9.进行测试

./hello_test /dev/xyz
./hello_test /dev/xyz 123

可以看到不输入123时只进行读操作,输入123时就开始写操作,是由argc==3判断的

5.注意事项

  1. 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和驱动的交互方式
  1. app和驱动之间通过copy_to _usercopy_from_user传递数据
  2. 驱动程序和硬件之间可以通过使用原始函数或者直接通过ioremap去映射寄存器地址后,直接访问寄存器
②APP使用驱动的四种方式

驱动程序:提供能力,不提供策略

  1. 非阻塞(查询):不会等待,有就是有没有就是没有,该实验代码就是非阻塞
  2. 阻塞(休眠-唤醒):等待有数据
  3. poll(定个闹钟):唤醒条件a时间到;b发生了中断
  4. 异步通知
③中断

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。

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值