Linux字符设备驱动_基础

Linux驱动入门(一)字符设备驱动基础 Linux驱动入门(一)字符设备驱动基础 Linux驱动入门系列 Linux驱动入门(一)字符设备驱动基础 Linux驱动入门(二)Led驱动 Linux驱动入门(三)轮询实现按键驱动 Linux驱动入门(四)中断实现按键驱动 Linux驱动入门(五)poll机制实现按键驱动 Linux驱动入门(六)使用定时器防按键抖动 文章目录Linux驱动入门(一)字符设备驱动基础一、驱动简介二、字符设备驱动... 阅读详情

1、 字符设备驱动

1.1 字符设备驱动的框架

在这里插入图片描述

1.2 注册字符设备驱动的函数

#include <linux/fs.h>
1. register_chrdev ----> 向内核注册字符设备
	int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
	功能:向内核注册字符设备驱动
    参数:
    	@ major : 主设备号
            major = 0 :主设备号由内核自动分配(不需要考虑主设备号是否被占用)
			major > 0 : major就是主设备号(需要考虑主设备号是否被占用)
                如何查看已经使用的设备号:cat /proc/devices
        @ name : 设备文件的名字
        @ fops : 操作方法结构体的指针变量的地址
	struct file_operations {
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        int (*open) (struct inode *, struct file *);
        int (*release) (struct inode *, struct file *);
    }; 

	返回值:
        major = 0 : 成功返回分配成功之后的主设备号,失败返回错误码
        major > 0 : 成功返回0, 失败返回错误码
    
     错误码: EIO , 通过EIO错误码查看其他的错误码
            由于错误码的宏定义是一个正数,因此再返回错误码时需要再前边加一个符号,
            例子 : return -EIO;
            #define	EPERM		 1	/* Operation not permitted */
            #define	ENOENT		 2	/* No such file or directory */
            #define	ESRCH		 3	/* No such process */
            #define	EINTR		 4	/* Interrupted system call */
            #define	EIO		 5	/* I/O error */
            #define	ENXIO		 6	/* No such device or address */
            #define	E2BIG		 7	/* Argument list too long */
            #define	ENOEXEC		 8	/* Exec format error */
            #define	EBADF		 9	/* Bad file number */
            #define	ECHILD		10	/* No child processes */
            #define	EAGAIN		11	/* Try again */
            #define	ENOMEM		12	/* Out of memory */
            #define	EACCES		13	/* Permission denied */
            #define	EFAULT		14	/* Bad address */
            #define	ENOTBLK		15	/* Block device required */
            #define	EBUSY		16	/* Device or resource busy */
            #define	EEXIST		17	/* struct file_operations */
            #define	EXDEV		18	/* Cross-device link */
            #define	ENODEV		19	/* No such device */
            #define	ENOTDIR		20	/* Not a directory */
            #define	EISDIR		21	/* Is a directory */
            #define	EINVAL		22	/* Invalid argument */
            #define	ENFILE		23	/* File table overflow */
            #define	EMFILE		24	/* Too many open files */
            #define	ENOTTY		25	/* Not a typewriter */
            #define	ETXTBSY		26	/* Text file busy */
            #define	EFBIG		27	/* File too large */
            #define	ENOSPC		28	/* No space left on device */
            #define	ESPIPE		29	/* Illegal seek */
            #define	EROFS		30	/* Read-only file system */
            #define	EMLINK		31	/* Too many links */
            #define	EPIPE		32	/* Broken pipe */
            #define	EDOM		33	/* Math argument out of domain of func */
            #define	ERANGE		34	/* Math result not representable */ 
2. unregister_chrdev  ---> 注销字符设备
    void unregister_chrdev(unsigned int major, const char *name)
    功能:注销字符设备驱动
    参数:
    	@ major : 主设备号
        @ name : 字符设备的名字
    返回值:无

1.3 字符设备驱动测试代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define MYNAME  "mycdev"

int major;

int mycdev_open (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}
int mycdev_close (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}

struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    // 注册字符设备
    major = register_chrdev(0, MYNAME, &fops);
    if (major < 0) {
        printk("register char device failed!\n");
        return -EIO;
    }
    return 0;
}

static void __exit mycdev_exit(void)
{
    unregister_chrdev(major, MYNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk <zhouk_bj@hqyj.com>");

1.4 应用层的代码实现


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char u_buf[100] = {0};
int main(int argc, const char *argv[])
{
    /*your code*/
    int fd;
    fd = open("/dev/mycdev", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    read(fd, u_buf, sizeof(u_buf));
    write(fd, u_buf, sizeof(u_buf));
    close(fd);
    return 0;
}

1.5 修改Makefile文件,编译应用层的代码


modname ?= 

arch ?= x86
ifeq ($(arch),x86)
	KERNELDIR := /lib/modules/$(shell uname -r)/build
	CROSS_COMPILE := 
else
	KERNELDIR := /home/linux/DC22041/porting/linux/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61/
	CROSS_COMPILE := arm-linux-gnueabihf-
endif

CURRENTDIR := $(shell pwd)

CC := $(CROSS_COMPILE)gcc
all:
	make -C $(KERNELDIR) M=$(CURRENTDIR) modules
	$(CC) test.c -o test
install:
	@cp $(modname).ko  ~/nfs/rootfs/ 
	@cp test ~/nfs/rootfs

help:
	@echo "make arch=arm|x86 modname=modules drivers source file name"

clean:
	make -C $(KERNELDIR) M=$(CURRENTDIR) clean
	
obj-m := $(modname).o

1.6 加载驱动并测试驱动的步骤

1> 加载驱动 : sudo insmod mycdev.ko
2> 查看驱动对应的主设备号 : cat /proc/devices, 需要查看自己的主设备号
3> 在/dev目录下创建设备文件 sudo mknod  /dev/mycdev c  236  0 
	
	mknod  : 创建设备文件的命令
	/dev/mycdev  : 将设备文件创建到/dev目录下,默认设备文件存到/dev目录下,当然也可以放到其他目录下
	c   : 表示字符设备文件,  如果写b:表示块设备文件
	236  : 主设备号
    0  : 次设备号
4> 运行应用程序: sudo ./test
5> 使用dmesg查看驱动中的open read write close函数是否被调用,如下图所示
6> 卸载驱动  sudo rmmod  mycdev

在这里插入图片描述

1.7 自己编写驱动代码的思路

1> 先写模块化驱动的三要素:入口,出口,许可证
2> 在入口函数中调用register_chrdev函数,函数需要什么就准备对应的变量
3> 在出口函数中调用unregister_chrdev函数
4> 编译驱动的程序,如果有错误进行修改,没有错误编写应用程序进行测试。

2、 用户空间和内核空间直接数据的拷贝

2.1 用户空间和内核空间数据拷贝的API

#include <linux/uaccess.h>
1. copy_to_user 
    unsigned long copy_to_user(void __user *to, const void *from, 
                               unsigned long n)
    功能:将内核空间中的数据拷贝到用户空间
    参数:
    	@ to : 用户空间的地址
        @ from : 内核空间的地址
        @ n : 拷贝数据的大小
    返回值:
         成功返回0,失败返回未拷贝的数据的个数
2. copy_from_user 
    unsigned long copy_from_user(void *to, const void __user *from, 
                                 unsigned long n)
    功能:将用户空间中的数据拷贝到内核空间
    参数:
    	@ to : 内核空间的地址
        @ from : 用户空间的地址
        @ n : 拷贝数据的大小
    返回值:
         成功返回0,失败返回未拷贝的数据的个数

2.2 系统调用函数和驱动中的read/write对应关系

在这里插入图片描述

2.3 用户空间和内核空间数据拷贝的驱动代码实现

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define MYNAME  "mycdev"

int major;
char kbuf[128] = "I am kernel spcae data!";
int mycdev_open (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{   
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    // 校准拷贝数据的大小
    if (size > sizeof(kbuf)) size = sizeof(kbuf);
    // 将内核空间的数据拷贝到用户空间
    ret = copy_to_user(ubuf,kbuf,size);
    if (ret) {
        printk("kernel space copy data to user space failed!\n");
        return -EINVAL;
    }
    // 成功返回拷贝成功的字节的个数
    return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
     // 校准拷贝数据的大小
    if (size > sizeof(kbuf)) size = sizeof(kbuf);
    // 将内核空间的数据拷贝到用户空间
    ret = copy_from_user(kbuf,ubuf,size);
    if (ret) {
        printk("user space copy data to kernel space failed!\n");
        return -EINVAL;
    }

    printk("kernel space = %s\n", kbuf);
    return size;
}
int mycdev_close (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}

struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    // 注册字符设备
    major = register_chrdev(0, MYNAME, &fops);
    if (major < 0) {
        printk("register char device failed!\n");
        return -EIO;
    }
    return 0;
}

static void __exit mycdev_exit(void)
{
    unregister_chrdev(major, MYNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk <zhouk_bj@hqyj.com>");

2.4 用户空间和内核空间数据拷贝应用层代码实现


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

char u_buf[128] = {0};
int main(int argc, const char *argv[])
{
    /*your code*/
    int fd;
    fd = open("/dev/mycdev", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    read(fd, u_buf, sizeof(u_buf));
    printf("user space = %s\n", u_buf);
    strcpy(u_buf, "I am user space data!");
    write(fd, u_buf, sizeof(u_buf));
    close(fd);
    return 0;
}

2.5 代码测试

1> 加载驱动 : sudo insmod mycdev.ko
2> 查看驱动对应的主设备号 : cat /proc/devices, 需要查看自己的主设备号
3> 在/dev目录下创建设备文件 sudo mknod  /dev/mycdev c  236  0 
4> 运行应用程序: 	
	linux@ubuntu:02mycdev$ sudo ./test 
	user space = I am kernel spcae data!
5> 使用dmesg查看驱动中的open read write close函数是否被调用,如下图所示
6> 卸载驱动  sudo rmmod  mycdev

在这里插入图片描述

3、led灯驱动的实现

3.1 在内核空间能否操作特殊功能寄存器地址

特殊功能寄存器的地址是物理地址,而内核空间的地址是3-4G的虚拟地址,
如果直接在内核空间操作特殊功能寄存器的物理地址空间,会导致内核出错。
如果想在内核空间操作特殊功能寄存器的物理地址,需要将物理地址映射为虚拟地址,
在内核空间操作虚拟地址最终相当于操作了特殊功能寄存器的物理地址。

mmu(memory manager unit) : 由内存管理单元完成物理地址到虚拟地址的映射。

使用ioremap函数完成物理地址到虚拟地址的映射; 使用iounmap函数完成取消物理地址到虚拟地址的映射。

3.2 物理地址到虚拟地址映射的API

#include <linux/io.h>
1. ioremap
	void  *ioremap(phys_addr_t paddr, unsigned long size)
	功能:完成物理地址到虚拟地址的映射
    参数:
    	@ paddr : 物理地址
    	@ size : 映射空间的大小,以字节为单位
	返回值:
    	成功返回虚拟的地址
    	失败返回NULL
2. iounmap
	void iounmap(const void __iomem *addr)
    功能:取消物理地址到虚拟地址的映射
    参数:
    	@ addr : 虚拟地址
    返回值:无  

3.3 完成LED灯的驱动

3.3.1 分析led1灯的电路图

在这里插入图片描述
在这里插入图片描述

3.3.2 分析芯片手册

1. RCC_MP_AHB4ENSETR[4]  ----> 0x50000A28 
	写1,使能GPIOE外设的时钟
2. GPIOx_MODER[21:20] ----> 0x50006000
	写0b01, 设置为通用的输出模式

3. GPIOx_OTYPER[10]  ----> 0x50006004
	写0b0 , 设置为推挽输出
	
4. GPIOx_OSPEEDR[21:20]  ----> 0x50006008
	写0b00, 设置为低速模式
	
5. GPIOx_PUPDR[21:20]  -----> 0x5000600C
	写0b00,禁止上拉和下拉

6. GPIOx_ODR[10] ----> 0x50006014
	写0, PE10输出低电平
	写1, PE10输出高电平

3.3.3 编写led灯的驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define MYNAME  "myled"
#define RCC_MP_AHB4ENSETR_PHY_ADDR  0x50000A28
#define GPIOE_MODER_PHY_ADDR    0x50006000
#define GPIOE_OTYPER_PHY_ADDR   0x50006004
#define GPIOE_OSPEEDR_PHY_ADDR  0x50006008
#define GPIOE_PUPDR_PHY_ADDR    0x5000600C
#define GPIOE_ODR_PHY_ADDR      0x50006014

volatile unsigned int * rcc_virl_addr;
volatile unsigned int * gpioe_moder_virl_addr;
volatile unsigned int * gpioe_otyper_virl_addr;
volatile unsigned int * gpioe_ospeedr_virl_addr;
volatile unsigned int * gpioe_pupdr_virl_addr;
volatile unsigned int * gpioe_odr_virl_addr;
int major;
char kbuf[128] = {0};
int myled_open (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{   
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    // 校准拷贝数据的大小
    if (size > sizeof(kbuf)) size = sizeof(kbuf);
    // 将内核空间的数据拷贝到用户空间
    ret = copy_to_user(ubuf,kbuf,size);
    if (ret) {
        printk("kernel space copy data to user space failed!\n");
        return -EINVAL;
    }
    // 成功返回拷贝成功的字节的个数
    return size;
}
/*
    需要指定一个led灯控制的协议
    kbuf[0] = 1 : 表示控制LED1灯
    kbuf[0] = 2 : 表示控制LED2灯
    kbuf[0] = 3 : 表示控制LED3灯
    kbuf[1] = 0 :表示熄灭LED灯
    kbuf[1] = 1 :表示点亮LED灯
*/
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
     // 校准拷贝数据的大小
    if (size > sizeof(kbuf)) size = sizeof(kbuf);
    // 将用户空间的数据拷贝到内核空间
    ret = copy_from_user(kbuf,ubuf,size);
    if (ret) {
        printk("user space copy data to kernel space failed!\n");
        return -EINVAL;
    }
    switch (kbuf[0]) {
    case 1:
        if (kbuf[1] == 1) 
            *gpioe_odr_virl_addr |= (0x1 << 10);
        else
            *gpioe_odr_virl_addr &= (~(0x1 << 10));
        break;
    case 2:
        break;
    case 3:
        break;
    default:
        break;
    }

    return size;
}
int myled_close (struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__,__LINE__);
    return 0;
}

struct file_operations fops = {
    .open = myled_open,
    .read = myled_read,
    .write = myled_write,
    .release = myled_close,
};
// 完成led灯的初始化,
int led_init(void)
{
    int ret = 0;
    // 完成物理地址到虚拟地址的映射
    rcc_virl_addr = ioremap(RCC_MP_AHB4ENSETR_PHY_ADDR, 4);
    if (rcc_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    gpioe_moder_virl_addr = ioremap(GPIOE_MODER_PHY_ADDR, 4);
    if (gpioe_moder_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    gpioe_otyper_virl_addr = ioremap(GPIOE_OTYPER_PHY_ADDR, 4);
    if (gpioe_otyper_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    gpioe_ospeedr_virl_addr = ioremap(GPIOE_OSPEEDR_PHY_ADDR, 4);
    if (gpioe_ospeedr_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    gpioe_pupdr_virl_addr = ioremap(GPIOE_PUPDR_PHY_ADDR, 4);
    if (gpioe_pupdr_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    gpioe_odr_virl_addr = ioremap(GPIOE_ODR_PHY_ADDR, 4);
    if (gpioe_odr_virl_addr == NULL) 
    {
        printk("memory map failed!\n");
        ret = -ENOMEM;
    }
    // 使用gpioe外设的时钟
    *rcc_virl_addr = (0x1 << 4);
    // 初始化GPIO引脚为输出,推挽输出,低俗模式,禁止上拉下拉,默认输出低电平
    *gpioe_moder_virl_addr &= (~(0x3 << 20));
    *gpioe_moder_virl_addr |= (0x1 << 20);
    *gpioe_otyper_virl_addr &= (~(0x1 << 10));
    *gpioe_ospeedr_virl_addr &= (~(0x3 << 20));
    *gpioe_pupdr_virl_addr &= (~(0x3 << 20));
    *gpioe_odr_virl_addr &= (~(0x1 << 10));
    return ret;
}
// 取消led灯的初始化
void led_delInit(void)
{
    iounmap(gpioe_odr_virl_addr);
    iounmap(gpioe_pupdr_virl_addr);
    iounmap(gpioe_ospeedr_virl_addr);
    iounmap(gpioe_otyper_virl_addr);
    iounmap(gpioe_moder_virl_addr);
    iounmap(rcc_virl_addr);
}
static int __init myled_init(void)
{
    int ret;
    // 注册字符设备
    major = register_chrdev(0, MYNAME, &fops);
    if (major < 0) {
        printk("register char device failed!\n");
        return -EIO;
    }

    ret = led_init();
    if (ret < 0) {
        printk("led init failed!\n");
        return ret;
    }
    return 0;
}

static void __exit myled_exit(void)
{
    led_delInit();
    unregister_chrdev(major, MYNAME);
}
module_init(myled_init);
module_exit(myled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk <zhouk_bj@hqyj.com>");

3.3.4 编写led灯的应用程序


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

char u_buf[128] = {0};
int main(int argc, const char *argv[])
{
    /*your code*/
    int fd;
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    while(1) {
        u_buf[0] = 1;
        u_buf[1] = 1;
        write(fd, u_buf, sizeof(u_buf));
        sleep(1);
        u_buf[1] = 0;
        write(fd, u_buf, sizeof(u_buf));
        sleep(1);
    }
    close(fd);
    return 0;
}

3.3.5 测试led灯的驱动和应用程序

1> 编译led灯的驱动及应用程序:make arch=arm modname=myled  all
2> 拷贝驱动程序及led灯的应用程序到跟文件系统中:make install
3> 完成开发板开发阶段的系统的启动
4> 加载驱动 : 
	[root@fsmp1a /] insmod myled.ko
5> 查看驱动对应的主设备号 : 
	[root@fsmp1a /] cat /proc/devices, 需要查看自己的主设备号
6> 在/dev目录下创建设备文件 
	[root@fsmp1a /] mknod  /dev/myled c  242  0 
7> 运行应用程序: 	
	[root@fsmp1a /] ./test 
8> 观看led灯的现象
9> 卸载驱动  
	[root@fsmp1a /] rmmod  mycdev
;
    }
    while(1) {
        u_buf[0] = 1;
        u_buf[1] = 1;
        write(fd, u_buf, sizeof(u_buf));
        sleep(1);
        u_buf[1] = 0;
        write(fd, u_buf, sizeof(u_buf));
        sleep(1);
    }
    close(fd);
    return 0;
}

3.3.5 测试led灯的驱动和应用程序

1> 编译led灯的驱动及应用程序:make arch=arm modname=myled  all
2> 拷贝驱动程序及led灯的应用程序到跟文件系统中:make install
3> 完成开发板开发阶段的系统的启动
4> 加载驱动 : 
	[root@fsmp1a /] insmod myled.ko
5> 查看驱动对应的主设备号 : 
	[root@fsmp1a /] cat /proc/devices, 需要查看自己的主设备号
6> 在/dev目录下创建设备文件 
	[root@fsmp1a /] mknod  /dev/myled c  242  0 
7> 运行应用程序: 	
	[root@fsmp1a /] ./test 
8> 观看led灯的现象
9> 卸载驱动  
	[root@fsmp1a /] rmmod  mycdev
Linux驱动开发(一)--字符设备驱动开发基础 本章会以一个虚拟的设备为例,讲解如何进行字符设备驱动开发,以及如何编写 测试 APP 来测试驱动工作是否正常,为以后的学习打下坚实的基础。 阅读详情

相关推荐

k8s 查看cpu使用率最高的pod

在 Kubernetes 中,可以使用命令查看 Pod 的资源使用情况,从而找到 CPU 使用率最高的 Pod。

u010674101的专栏 3345

Linux驱动——字符设备驱动

1.使用什么工具来写驱动代码:安装source insight工具(1)找到软件提示把工具安装激活(2)把Linux内核代码解压到windows目录中(3)打开工具添加查看的项目第一个对话框:第一个文本框:输入工程名字第二个文本框:路径(默认)第二个对话框: 在项目源码位置,选择 Linux内核代码位置 点击OK 第三个对话框: 选择需要查看的Linux内核代码的包含的源码文件(要查看的内核目录)需要添加的目录:driver/iicinclude。

Linux驱动开发工程师,熟悉Linux/Android底层驱动、TP、显示、IIC、网络等通用外设调试适配,Linux上层应用开发。常用平台瑞芯微、全志、恩智浦等。 3439

SNAP实战:哨兵1 SLC数据预处理全流程详解(附避坑指南)

本文详细解析了使用SNAP软件处理哨兵1号SLC数据的完整预处理流程。从轨道精校正、辐射定标到多视与斑点滤波,再到地理编码,文章提供了清晰的操作步骤与核心逻辑,并重点分享了实践中的常见“坑”与解决方案,旨在帮助用户高效、准确地完成SAR数据预处理。

7up55的博客 1027

【Linux设备驱动】如何挂载和卸载驱动 lsmod insmod rmmod和modprobe

如何挂载卸载以及查看linux设备驱动呢,主要包含insmod,rmmod,lsmod以及modprobe

Tschome的博客 6371

融合ZILN损失与深度学习的LTV预测:从概率分布到精准营销决策

本文探讨了融合ZILN损失与深度学习的LTV预测方法,通过深度概率模型精准预测用户生命周期价值(LTV),优化营销决策。文章详细解析了ZILN损失函数的优势、模型架构设计要点及工程化落地实践,帮助企业在预算分配、用户分层等场景实现数据驱动的精准营销。

weixin_29093169的博客 308

基于h5端的视频录制和回溯功能的实现

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

发果叁 3191

linux c 错误码大全及错误的捕获

比较 麻烦的是每次都要去linux源代码里面查找错误代码的含义,现在把它贴出来,以后需要查时就来这里看了。经常在调用linux 系统api 的时候会出现一些错误,比方说使用open() write() creat()之类的函数有些时候会返回-1,也就是调用失败,这个时候往往需要知道失败的原因。在程序代码中包含 #include ,然后每次程序调用失败的时候,系统会自动用用错误代码填充errno这个全局变量,这样你只需要读errno这个全局变量就可以获得失败原因了。

墨痕诉清风的博客 6025

Android Studio中把llama.cpp编译Android .so

本文介绍了一个在Android Studio中集成llama.cpp的最小Demo实现方案,通过JNI将llama.cpp编译为Android.so库,实现本地加载GGUF模型进行文本生成。文章详细说明了项目结构配置(SDK≥30,NDK≥28)、CMake编译设置、Kotlin-JNI-C++的完整调用链实现,以及模型文件部署方式。该Demo采用每次请求新建上下文的简化设计,包含加载模型、生成文本、资源释放等核心功能,并提供了错误排查建议。项目舍弃了上下文保持等复杂功能以确保稳定性,适合作为端侧大语言模型

Zhang Phil 521

创建硬链接:Invalid cross-device link错误

http://www.cppblog.com/mingjiagu/archive/2011/12/01/161271.html test243:~ # ln -d /usr/local/named/etc/named.conf /etc/named.conf ln: creating hard link ’ /etc/… /etc/named.conf’ :Invalid cross-device...

weixin_44825564的博客 1万+

1. 在 SAP ABAP 事物码 SEGW 里创建 SAP OData 项目

本文介绍如何在 SAP ABAP 事物码 SEGW 里创建 SEGW 项目,使用工具生成 Runtime 运行时需要的资源。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 5443

EPO: Hierarchical LLM Agents with Environment PreferenceOptimization

长期决策和规划任务依然是基于大型语言模型(LLM)的智能体面临的一大挑战(Valmeekam 等,2023;Liu 等,2023;Silver 等,2024)。这些任务需要智能体进行多步骤的规划,同时保持行为的一致性和目标导向,这对于通常被设计用于即时和局部预测的大型语言模型来说颇为困难。此外,针对具身智能体的大型语言模型微调面临的一个关键难题是大规模标注数据的需求(Reed 等,2022)。

qq_52053775的博客 142

[转载]Android系统上(mv不可用)cp命令的替代方法

Android系统上cp命令的替代方法 情况是这样的:该台Android手机不提供adb root的权限,但又不能装破解root权限的软件,若要往Android手机的/system/和/data/分区下拷一些文件, 是否有其它方法可以将文件复制到上述的分区中?当然,手机里的文件管理器没有提供将文件从存储卡上复制到这两个分区的功能,如果有哪个Android文件管理器提供类似的功能,在此也...

weixin_30797027的博客 503

Flink Gauss CDC:深度剖析存量与增量同步的创新设计

开发Flink Gauss CDC是为了适配华为自研GaussDB的特殊日志格式和逻辑复制机制,解决Flink CDC无法直接支持的问题,同时优化存量与增量同步的效率和一体化读取能力,满足GaussDB复合主键处理、数据落库等定制化需求,以实现高效、准确的数据同步。

数据与算法架构提升之路专栏 1985

【linux驱动之字符设备驱动基础】

linux驱动之字符设备驱动基础初探 文章目录linux驱动之字符设备驱动基础初探前言一、开启驱动学习之路二、驱动预备知识三、什么是驱动?3.1 驱动概念3.2 linux 体系架构3.3 模块化设计3.4 linux设备驱动分类3.5 驱动程序的安全性要求四、环境搭建4.1 内核源码树构建4.2 nfs服务器搭建五、简单的模块源码分析5.1 常用模块操作命令5.2 模块的安装5.3 函数修饰符5.4 printk函数解析5.5 Makefile 分析六、驱动编写实践总结 前言 内容学习来自朱有鹏老

boybs的博客 1509

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

底软驱动 | Linux字符设备驱动开发基础

/fops结构体dev_t dev;//设备号(包含主设备号和次设备号);dev_t类型:typedef u_long dev_t;cdev_alloccdev_initcdev_addcdev_del(2)设备号:主设备号+次设备号MKDEV:由一个主设备号和次设备号算出设备号MAJOR:从设备号提取主设备号MINOR:从设备号提取次设备号代码示例:新的接口注册字符设备驱动需要两步(定义一个全局cdev结构体)│├── 模块安装函数xxx。

叫好与叫座虽然不是对立面,但想在同一个作品中达到双重效果很难。 968

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Linux驱动开发——字符设备驱动基础

Linux字符设备驱动基础

我有一壶酒 654

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

整理--Linux字符设备驱动开发基础

知识整理–Linux字符设备驱动开发基础 linux驱动:封装对底层硬件的操作,向上层应用提供操作接口

Liam的博客 1924

Linux驱动开发框架基础——新旧字符设备驱动笔记整理(1)

​ 首先,各位需要知道的是。广义的说——Linux驱动开发的开发对象一般分成三个大类:字符设备,块设备和网络设备。实际上,Linux的驱动开发,笔者倾向于认为是在Linux的大框架下告知我们的Linux如何操作我们给定的设备。所以,想好我们如何操作设备是驱动开发的核心。打开?关闭?读?写?以及其他的属性。现在,我们就来开始尝试一下学习Linux驱动开发。

charlie114514191的博客 1188

Linux字符设备驱动基础知识

  在Linux设备驱动中,字符设备驱动较为基础,字符设备即一个一个的字节,按照字节流进行读写操作的设备,读写数据具有一定的先后顺序,例如I2C、SPI、LCD等都属于字符设备。

Eddy的博客 780

嵌入式Linux驱动开发初级-字符设备驱动基础框架

嵌入式Linux驱动开发初级-字符设备驱动基础框架

found_1的博客 705

linux 驱动基础 - 一、字符设备

该结构体是系统调用与驱动连接的桥梁,当我们在应用层使用 open 函数打开一个设备的时候,内核会创建一个 file 结构并关联 file_operations 中的一组函数,最终会调用到驱动中关联的 file_operations 结构体实例中 open 函数。实际上并没用到所有的 12 位,在老式内核中主设备号的范围为 0-255。向系统删除一个cdev,完成字符设备的卸载,对 cdev_del 的调用通常发生在字符设备驱动模块卸载函数中,当 cdev_del 被调用后对应的cdev结构就不应被访问了。

sty01z的博客 2102

Linux字符设备驱动基础

本文主要介绍了Linux系统下进行内核字符设备的驱动开发基础,简单实现了一个内存模拟的字符设备的读写和IO操作流程

一个懒癌的驱动工程师的随笔分享 347

Linux字符设备驱动基础(五)

Linux 字符驱动基础(五) 8 文件操作集 8.1 文件操作集概述 struct file_operations { struct module *owner; //拥有该结构的模块指针,一般有THIS_MODULES loff_t (*llseek) (struct file *, loff_t, int); //用来修改文件当前读写的位置 ssize_t (*read) (struc...

枫潇潇 485

Linux字符设备驱动基础(二)

Linux字符设备基础(二) 5 设备号相关操作 设备号由主设备号和次设备号组成。主设备号用来标识与设备文件相连的驱动程序,用以反映设备类型;次设备号是用于驱动程序来辨别所操作的具体设备,用以区分同类型的设备。 linux内核中,设备号用dev_t来描述,Linux 4.14.14中定义如下: typedef u_long dev_t; 在32位机中是4个字节,高12bit表示主设备号,低20b...

枫潇潇 333

深入解析Linux字符设备驱动开发:从基础到实践

字符设备驱动开发是 总结理解设备模型和驱动框架熟悉内核API和机制通过实践项目积累经验学会调试和测试方法持续学习内核源码和文档从简单的虚拟设备开始,逐步挑战真实硬件驱动,是成为Linux驱动开发专家的有效路径。记住:内核开发需要耐心和实践,每个优秀的驱动开发者都是从第一个"Hello World"模块开始的。

niuTyler的博客 682

Linux驱动开发基础之字符设备驱动

本文介绍了字符设备开发相关知识,其重点是字符设备驱动的开发步骤。对于初学者最重要的是有一个设备驱动开发的整体概念,如果感到懵逼就先跳过,先手敲个简单的GPIO驱动,例如LED、蜂鸣器、按键等,有了实践经验很容易就能理解了。

LY1203531955的博客 866
上一篇: Linux驱动模块
下一篇: Linux字符设备驱动_进阶
niuiniuiniu
博客等级 码龄7年 8粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值