基于Linux的字符设备驱动框架(使用中断、poll、异步通知、同步互斥阻塞)

Linux驱动面试题 当一个进程获取到自旋锁之后,如果此时有另外一个进程也想获取这把锁,此时后一个进程处于自旋状态(原地打转的状态)。自旋锁又叫做忙等锁。当一个进程获取到信号量之后,如果此时有另外一个进程也想获取这个信号量,此时后一个进程处于休眠状态。休眠状态是不需要消耗CPU资源。当一个进程获取到互斥体之后,如果此时有另外一个进程也想获取这个互斥体,此时后一个进程处于休眠状态。互斥体又叫做排它锁。原子操作名字就是根据它的特性得到的,在使用原子操作的时候如果执行了原子操作。 阅读详情

1 字符设备驱动

所用内核:Linux-2.6.22.6

  1. 执行 insmod xxx.ko 加载驱动程序
  2. 执行 rmmod xxx 卸载驱动程序
  3. 执行 cat /proc/devices 查看内核支持驱动程序和主设备号
  4. 执行 cat /proc/interrupts 查看中断
  5. 执行 mknod /dev/xxx c major 0 创建设备节点
  6. 执行 ls /dev/ 查看设备节点

1.1 驱动框架

当APP调用open、write、read…时,对应的内核虚拟文件系统(VFS)便会调用sys_open、sys_write、sys_read…,然后这些系统函数会去调用驱动中对应的xxx_open、xxx_write、xxx_read…,在驱动程序中需要实现这些函数的功能以便内核调用。


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

#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

/* volatile unsigned long *gpfcon = NULL; */
/* volatile unsigned long *gpfdat = NULL; */
struct class *xxx_class;
struct class_device *xxx_class_dev;

/* 应用程序对设备文件 /dev/xxx 执行open(...)时,
 * 就会调用 xxx_open函数
 */
static int xxx_open(struct inode *inode, struct file *file)
{
	/* xxx_open函数实现 */
...
	return 0;
}

/* 应用程序对设备文件 /dev/xxx 执行write(...)时,
 * 就会调用 xxx_write函数
 */
static ssize_t xxx_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
    
	copy_from_user(&val, buf, count);	/* 内核空间从用户空间得到数据 */
	//copy_to_user(&val, buf, count);	/* 用户空间从内核空间得到数据 */

	/* 对用户空间传回的值进行处理 */
...
	return 0;
}

/* 这个结构是字符设备驱动程序的核心 
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数。
 * 结构体 file_operations 中存放的都是函数指针
 */
static struct file_operations xxx_fops = {
	.owner	= THIS_MODULE,		/* 这是一个宏,指向编译模块时自动创建 __this_module变量 */
	.open	= xxx_open,	
	.write	= xxx_write,
    .read	= xxx_read,
    .poll	= xxx_poll,		/* 处理poll机制 */
    .fasync	= xxx_fasync,	/* 处理异步通知 */
};

#define DEVICE_NAME     "xxx"  /* 加载模式后,执行”cat /proc/devices”命令后可以看到的设备名称 */
#define xxx_MAJOR       0      /* 主设备号, 设为 0 是为了让系统自动分配主设备号,其值也可以自己设定 */
static struct class *leds_class;	/* 创建类 */
static struct class_device	*leds_class_devs; /* 在创建的类下创建设备节点 */

int major;	/* 让系统自动分配主设备号 */
/* 执行 insmod 命令时就会调用这个函数 */
int xxx_init(void)
{
	/* 注册字符设备,注册完后只会将 xxx_fops 填充进数组,但是并不会生成系统信息
	 * 参数为主设备号、设备名字、file_operations结构
	 * 这样主设备号就和具体的 file_operations 结构联系起来了
	 * 操作主设备号为 xxx_MAJOR 的设备文件时,就会调用 xxx_fops 中的相关成员函数
     * 函数原型:int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) 
     */
	major = register_chrdev(xxx_MAJOR, DEVICE_NAME, &xxx_fops);	

    /* 以下用来创建系统信息 
     * 函数原型:struct class *class_create(struct module * owner, const char * name);
     */
	xxx_class = class_create(THIS_MODULE, DEVICE_NAME);	/* 创建 DEVICE_NAME 类 */
    if (IS_ERR(xxx_class))	/* 判断创建成功与否 */
		return PTR_ERR(xxx_class);
    
    /* 函数原型:struct class_device *class_device_create(struct class * cls, struct class_device * parent, dev_t devt, struct device * device, const char * fmt,...); */
	xxx_class_dev = class_device_create(xxx_class, NULL, MKDEV(major, 0), NULL, "xxx"); /* majior 为主设备号,0 为次设备号(可以设为其他值), 在 DEVICE_NAME 类 下创建 xxx 的设备节点, 存在于 /dev/xxx */
    if (unlikely(IS_ERR(xxx_class_dev)))	/* 判断创建成功与否 */
		return PTR_ERR(xxx_class_dev);
    
	/* 当要操作开发板中的寄存器时,需要通 ioremap 来进行地址映射
	 * 函数原型:void __iomem *ioremap (unsigned long phys_addr, unsigned long size)
	 * gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	 * gpfdat = gpfcon + 1;
	*/
    
	return 0;
}

/* 执行 rmmod 命令时就会调用这个函数 */
static void xxx_exit(void)
{
	unregister_chrdev(major, DEVICE_NAME);	  /* 卸载驱动程序,函数原型:int unregister_chrdev(unsigned int major, const char *name) */
	class_device_unregister(xxx_class_dev);	  /* 卸载设备节点,函数原型:void class_device_unregister(struct class_device *class_dev) */
	class_destroy(xxx_class);				 /* 卸载创建的类,函数原型:void class_destroy(struct class *cls) */
	/* iounmap(gpfcon);						取消地址映射,函数原型:void iounmap(void *addr) */
}

/* 指定驱动程序的初始化函数和卸载函数 */	
module_init(xxx_init);	
module_exit(xxx_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_LICENSE("GPL");

1.1.1 应用程序(main)

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

int main(int argc, char **argv)
{
	int fd;
	fd = open("/dev/xxx", O_RDWR);	/* 打开设备节点, xxx需要和驱动中创建的设备节点相同*/
	if (fd < 0)		/* 判断设备节点是否存在 */
	{
		printf("can't open!\n");
	}

    /* 一些其他的处理
     * write(fd, void *buf, int buflen)   向内核写值 
     * read(fd, char __user * userbuf, size_t count)  从内核读取值
     */
	return 0;
}

1.2 驱动中加中断

因为查询方式太耗费CPU资源(99%),所以用中断方式来减少对应用程序对CPU的消耗。

/* 在驱动框架中添加 */
#include <linux/irq.h>

struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
	{S3C2410_GPF0, 0x01},
	{S3C2410_GPF2, 0x02},
	{S3C2410_GPG3, 0x03},
	{S3C2410_GPG11, 0x04},
};

static DECLARE_WAIT_QUEUE_HEAD(xxx_waitq);	/* 定义等待队列头结构体 */

/* 中断事件标志,中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;

/* 中断处理函数, 当发生中断了就会执行该函数 */
static irqreturn_t xxx_irq(int irq, void *dev_id)
{
	/* 一些中断处理程序 */
 ...
     ev_press = 1; 	/* 表示中断发生了 */
	 wake_up_interruptible(&xxx_waitq); 	/* 当中断发生了就唤醒休眠的进程 */
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int xxx_drv_open(struct inode *inode, struct file *file)
{
	/* 注册中断 
	 * int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *devname, void *dev_id)
	 */
	request_irq(IRQ_EINT0,  xxx_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
 ...
	return 0;
}

ssize_t xxx_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	/* 如果没有中断发生,就休眠 */
	wait_event_interruptible(xxx_waitq, ev_press);	/* 判断 ev_press的值,如果 ev_press = 0 就会休眠,如果休眠被唤醒就会从这继续向下执行程序 */

	/* 如果有按键按动作
	 * 一些处理程序 
	 * copy_to_user(buf, &key_val, 1);
	 */
 ...
	ev_press = 0;	/* 将中断标志清 0, 让程序休眠 */
	
	return 1;
}

/* 关闭设备时,用来释放中断 */
int xxx_close(struct inode *inode, struct file *file)
{
    /* 释放中断 
     * void free_irq(unsigned int irq, void *dev_id)
     */
	free_irq(IRQ_EINT0, &pins_desc[0]);
...
	return 0;
}

static struct file_operations xxx_fops = {
...
    /* 为释放中断添加函数 */
	.release = third_drv_close,
};

1.2.1 应用程序(main)

/* 添加以下代码 */
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;

	fd = open("/dev/xxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	while (1)	/* 一直查询有无中断产生, */
	{
		read(fd, &key_val,1);	/* 如果没有中断产生永远都不会返回 */
		printf("key_val = 0x%x\n", key_val);
	}

1.3 驱动中加poll机制

poll机制也是一种查询,但是它在查询过程中也可以休眠,如果有事件发生了就立刻返回,若在规定时间内没有事件发生就会返回(中断是只有中断发生之后才返回),对于系统调用 poll 或 select,它们对应的内核函数都是sys_poll。

/* 在驱动框架中添加 */
#include <linux/poll.h>

static DECLARE_WAIT_QUEUE_HEAD(xxx_waitq);

/* 中断事件标志,中断服务程序将它置1,fourth_drv_read将它清0 */
static volatile int ev_press = 0;

static irqreturn_t xxx_irq(int irq, void *dev_id)
{
	/* 和1.2中的xxx_irq相同 */
}

ssize_t xxx_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	/* 和1.2中的xxx_drv_read相同 */
}

static unsigned xxx_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &xxx_waitq, wait);	/* 不会立即休眠,将进程挂到 xxx_waitq 队列中去  函数原型:static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)*/

	if (ev_press)
	{
		mask |= POLLIN | POLLRDNORM;
	}
	return mask;
}

static struct file_operations xxx_fops = {
...
    /* 为 poll 添加函数 */
	.poll 	 = xxx_poll,
};

1.3.1 应用程序(main)

/* 添加以下代码 */
#include <poll.h>

int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	int ret;

	struct pollfd fds[1];	/* poll 机制可以同时查询多个驱动程序 */
    /* struct pollfd {
			int fd;
			short events;
			short revents;
	}; */
	fds[0].fd		= fd;
	fds[0].events	= POLLIN;	/* POLLIN 表示期待有数据读取 */
	while (1)
	{
		ret = poll(fds, 1, 5000); /* 函数原型:static unsigned int poll(struct file *file, struct socket *sock, poll_table *wait) */
		if (ret == 0)	/* ret=0 表示超时 */
		{
			printf("time out!\n");
		}
		else
		{
			read(fd, &key_val,1);
			printf("key_val = 0x%x\n", key_val);
		}
	}
	
	return 0;
}

1.4 驱动中加异步通知

查询、中断和poll 都是应用程序主动地去查询。异步通知为当有事件发生时驱动程序就去会提醒应用程序读取相应的值,它使用signal(信号量,用于进程间发信号),

进程间发信号的要点

  1. 应用程序:注册信号处理函数
  2. 谁发: 驱动发
  3. 发给谁: 发给应用程序(应用程序要告诉驱动PID)
  4. 怎么发: kill_fasync
/* 在驱动框架中添加 */
static struct fasync_struct *xxx_async;
/* struct fasync_struct {
	int	magic;
	int	fa_fd;
	struct	fasync_struct	*fa_next;  //singly linked list 
	struct	file 		*fa_file;
}; */
...
static irqreturn_t xxx_irq(int irq, void *dev_id)
{
...
     ev_press = 1; 	/* 表示中断发生了 */
	 wake_up_interruptible(&xxx_waitq); 	/* 唤醒休眠的进程 */
	
	 /* 3、在设备资源可获得时,调用kill_fasync()函数激发相应的信号 
	 * 函数原型:void kill_fasync(struct fasync_struct **fp, int sig, int band)
	 */
	 kill_fasync(&xxx_async, SIGIO, POLL_IN);
	
	return IRQ_RETVAL(IRQ_HANDLED);
}
...
static int xxx_fasync(int fd, struct file *filp, int on)
{
    /* 函数原型:int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) */
	return fasync_helper(fd, filp, on, &xxx_async);
}

static struct file_operations xxx_fops = {
...
	.fasync  = xxx_fasync,
};

1.4.1 应用程序(main)

/* 添加以下代码 */
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

int fd;

void my_signal_fun(int signum)
{
	unsigned char key_val;
	read(fd, &key_val,1);
	printf("key_val: 0x%x\n", key_val);
	
}

/* fasync */
int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int oflags;

	signal(SIGIO, my_signal_fun);  /* SIGIO表示IO口有数据,sighandler_t signal(int signum, sighandler_t handler); */

	fd = open("/dev/xxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't opren!\n");
	}

	fcntl(fd, F_SETOWN, getpid());	 /* 1、告诉内核,发给谁, 在 F_SETOWN 控制命令处理中设置filp->f_owner为对应进程ID ,此项工作已由内核完成,设备驱动无须处理*/
	oflags = fcntl(fd, F_GETFL);	/* 修改标志 */
	fcntl(fd, F_SETFL, oflags | FASYNC); /* 2、支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的xxx_fasync()函数将得以执行,驱动中应该实现xxx_fasync()函数 */	
 
	while (1)
	{
		sleep(1000);
	}
	
	return 0;
}

1.5 驱动中加同步互斥阻塞

1.5.1 原子操作

原子操作指的是在执行过程中不会被别的代码路径所中断的操作。常用原子操作函数举例

  1. atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为 0
  2. atomic_read(atomic_t *v); //返回原子变量的值
  3. void atomic_inc(atomic_t *v); //原子变量增加 1
  4. void atomic_dec(atomic_t *v); //原子变量减少 1
  5. int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false
/* 在驱动框架中添加 */
...
static atomic_t canopen = ATOMIC_INIT(1);		/* 定义原子变量并初始化为 1 */
...
static int xxx_drv_open(struct inode *inode, struct file *file)
{
	if (!atomic_dec_and_test(&canopen))	/* 只能允许一个应用程序运行,后面要使用应用程序将会被杀死 */
	{
		atomic_inc(&canopen);
		return -EBUSY;
	}
...
    
	return 0;
}
  
int xxx_drv_close(struct inode *inode, struct file *file)
{
	atomic_inc(&canopen);
...
	return 0;
}

1.5.2 信号量

信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码,当获取不到信号量时,进程进入休眠等待状态。

  1. 定义信号量

    struct semaphore sem;

  2. 初始化信号量

    void sema_init (struct semaphore *sem, int val);

    void init_MUTEX(struct semaphore *sem); //初始化为0

    static DECLARE_MUTEX(xxx_lock); //定义互斥锁

  3. 获得信号量
    void down(struct semaphore * sem);
    int down_interruptible(struct semaphore * sem);
    int down_trylock(struct semaphore * sem);

  4. 释放信号量
    void up(struct semaphore * sem);

/* 在驱动框架中添加 */
...
static DECLARE_MUTEX(xxx_lock);	/* 定义互斥锁 */
...
static int xxx_drv_open(struct inode *inode, struct file *file)
{
		/* 获取信号量 */
		down(&xxx_lock);	/* 第一个应用程序会获得信号量,后面的应用程序将不会获得信号量从而进入休眠(前面的应用程序用完后将顺序获得信号量) */
...
	return 0;
}

int xxx_drv_close(struct inode *inode, struct file *file)
{
...
	up(&xxx_lock);	/* 释放信号量 */
	return 0;
}

1.5.3 阻塞和非阻塞

阻塞操作 是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列中移走,直到等待的条件被满足。

非阻塞操作 进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

fd = open("…", O_RDWR | O_NONBLOCK); 传入 O_NONBLOCK 参数则为非阻塞,不传入 O_NONBLOCK 则为阻塞

/* 在驱动框架中添加 */
...
static int xxx_drv_open(struct inode *inode, struct file *file)
{
	if (file->f_flags & O_NONBLOCK)		/* 非阻塞 */
	{
		if (down_trylock(&xxx_lock))	/* 如果无法获取信号量就立马返回 */
		{
			return -EBUSY;
		}
	}
	else	/* 阻塞 */
	{
		/* 获取信号量 */
		down(&xxx_lock);	/* 如果无法获取信号量就进入休眠状态 */
	}
...
	return 0;
}

ssize_t xxx_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
...
	if (file->f_flags & O_NONBLOCK)		/* 非阻塞 */
	{
		if (!ev_press)
		{
			return -EAGAIN;
		}
	}
	else
	{
		/* 如果没有按键按动作,就休眠 */
		wait_event_interruptible(button_waitq, ev_press);
	}
...
	ev_press = 0;
	return 1;
}
Ubuntu使用gstreamer开发USB相机应用(一) Ubuntu使用gstreamer开发USB相机应用(一) 包括:gstreamer安装,测试。使用v4l2测试USB相机。使用gstreamer采集相机 阅读详情

相关推荐

离心泵级间导叶的设计计算

离心泵级间导叶的设计计算高速离心泵级间流道的作用是将第一级复合叶轮流出的高速液流收集起来并送入第二级复合叶轮。级间流道的设计应考虑降低液体的流速,实现动能向压力能转化,减小液体的水力损失。级间流道主要有蜗壳形流道和导叶两种结构形...

cqzc55322的博客 2566

字符设备驱动(8)-信号驱动,又名异步通知

在前面的I/O模型中,用户态程序都是主动获取设备的资源信息,而异步通知是当设备资源可用时,有驱动主动通知用户态程序,在由用户态程序发起读写操作。这种机制和中断非常相似,可以借用中断的思路来理解这一过程。

qq_40896451的博客 908

NXP S32K1 SPI驱动实战:从配置到DMA传输的完整流程解析

本文深入解析了NXP S32K1系列MCU的SPI驱动开发全流程。基于S32 Design Studio环境,详细阐述了从引脚配置、LPSPI模块初始化,到实现阻塞、中断及DMA三种传输模式的实战代码与配置要点。文章重点剖析了DMA传输的配置细节与性能优化,旨在帮助开发者构建高效、稳定的嵌入式SPI通信,并提供了SPI Flash驱动的综合案例。

blue42的博客 716

字符设备驱动之异步通知

注:代码有完整的注释,方便阅读 开发环境: ubuntu18.04 平台: JZ2440 kernel: Linux-3.4.2 交叉编译工具: arm-linux-gcc-4.4.3 驱动程序如下: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/dela

qq_42230338的博客 1351

FFMPEG音频开发: Linux下采集摄像头(使用V4L2框架)数据录制成MP4视频保存到本地

一、环境介绍 操作系统介绍:ubuntu 18.04 FFMPEG版本: 4.4.2 摄像头: USB摄像头、虚拟机挂载本机自带摄像头 二、FFMPEG与X264下载编译 X264下载地址: http://www.videolan.org/developers/x264.html FFMPEG下载地址:https://ffmpeg.org/download.html Yasm ...

8872

Pyhon在振动信号处理中的高级应用(一):振动信号特征(时域、频域)提取

文章目录一、概述二、算法原理2.1 时域统计特征三、python代码实现 一、概述 二、算法原理   这里我们借鉴西安交通大学机械学院雷教授的论文《Fault diagnosis of rotating machinery based on multiple ANFIS combination with GAs》相关研究成果。雷教授在论文中共列举了4类共24种振动信号的时域和频域特征。 2.1 时域统计特征 p1=∑n=1Nx(n)NP2=∑n=1N(x(n)−p1)2N−1p3=(∑n=1N∣x(n)∣N

白银时代的博客 1万+

1.字符设备驱动------Linux中断处理体系结构

一、中断处理体系结构的初始化   Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。 1.先了解中断...

weixin_30388677的博客 147

OpenClaw小白使用全攻略

OpenClaw全能技能解锁指南:从基础到进阶 摘要: 本文提供OpenClaw AI智能体的完整使用教程,帮助用户从安装后"不知道如何用"到熟练掌握核心功能。教程包含:1)控制台5大区域详解和3个必做初始化设置;2)文件管理、浏览器自动化、文档生成等5大基础功能实操,每个功能配有详细操作步骤、指令模板和效果演示;3)10+高频场景解决方案,如整理文件、搜集资料、写总结等;4)进阶技巧和常见问题解答。通过清晰的图文指引,即使电脑小白也能快速上手,实现办公自动化、提升工作效率。

通俗易懂学IT 3059

字符设备之poll机制

poll机制作用:相当于一个定时器,时间到了还没有资源就唤醒进程。 主要用途就是:进程设置一段时间用来等待资源,如果时间到了资源还没有到来,进程就立刻从睡眠状态唤醒不再等待。当然这只是使用于这段时间以后资源对于该进程已经无用的情况。 内核中poll机制的实现过程: sys_poll函数在include/linux/syscalls.h中声明 //函数定义前加宏asmlinkage

2621593260的专栏 2935

65页PPT全面学习大型汽车集团企业IT信息化(管理架构、应用架构、技术架构)

订单到交付(OTD):借助ERP、MES、LES、SRM、QMS、APS等系统,实现财务与业务一体化、高级排产、生产过程自动化及供应链协同。- 数据标准化:数据标准化是企业信息化建设基石,包括制定统一业务解释和标准、维护管理流程、发布应用等,涉及数据统一定义、分类、编码等内容。- 大数据应用:汽车行业大数据应用涵盖生产、销售、售后等环节,如设备预见性保养、销量预测、质量预警等,提升企业运营效率和决策能力。- 策略明晰:明确C2M管理矩阵,包括供应商、采购、物流、制造、销售、财务等环节的策略。

cdfunlove的博客 1049

OpenClaw 实战:7个真实场景的多 Agent 落地经验

《OpenClaw实战:7个真实场景的多Agent落地经验》摘要: 作者Maynor分享了从单Agent转向多Agent架构的实战经验。最初遇到的单Agent记忆混乱问题(如Flutter助手被C++话题干扰),通过拆分专项Agent(flutter-tutor/cpp-tutor等)并配置独立workspace解决。随后面临的技能调用混乱问题,采用"基础通用能力+专属能力"分层策略,通过Clawhub管理工具市场,将工具调用准确率从60%提升至95%。最后探讨多Agent协同方案,引入

Maynor的博客 1411

Linux驱动实践:中断处理函数如何【发送信号】给应用层?

文章目录驱动程序示例代码全貌Makefile 文件编译、测试应用程序示例代码全貌编译、测试 别人的经验,我们的阶梯! 大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【中断程序如何发送信号给应用层】。 最近分享的几篇文章都比较基础,关于字符类设备的驱动程序,以及中断处理程序。 也许在现代的项目是用不到这样的技术,但是万丈高楼平地起。 只有明白了这些最基础的知识点之后,再去看那些进化出来的高级玩意,才会有一步一个脚印的获得感。 如果缺少了这些基础的环节,很多深层次的东西,学起来就有点空中楼阁的感觉。 就好

IOT物联网小镇 5218

基于Simulink的电动车PMSM能量泄放与回收系统仿真设计

摘要 本文设计了一个基于Simulink的电动车PMSM能量泄放与回收系统仿真模型。系统采用分层建模方案,包含永磁同步电机(PMSM)矢量控制、电池管理和泄放功率计算等核心模块。通过详细的参数计算和MATLAB脚本实现,模拟了电动车在制动时的能量回收过程。重点研究了泄放电阻的控制策略,提出基于母线电压和电池SOC的多目标优化算法,确保系统在保护硬件安全的同时最大化能量回收效率。仿真模型包含完整的数学推导和模块搭建指南,为电动车能量管理系统设计提供了有效的仿真验证平台。

huanghm88的专栏 193

Part13:按键驱动程序的不同实现(查询/中断/poll/异步通知/同步互斥阻塞)

0 写在前面 part12文章是对ARM架构下Linux内核的异常(中断)处理体系结果的原理讲述,有了这些基本的认识后,下面根据不同实现方式 实现按键驱动程序的编写。 主要内容如下: 查询——霸占CPU资源 中断—— 说明一点:下面的实现不会贴全部代码,代码可从https://blog.youkuaiyun.com/qq_42800075/article/details/105670841获取 1 按键驱动...

MK 476

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

字符设备驱动程序——按键中断之互斥阻塞操作

一、互斥操作 在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为&amp;amp;amp;amp;amp;quot; 互斥锁&amp;amp;amp;amp;amp;quot; 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 实现方式 原子操作 原子操作指的是在执行过程中不会被别的代码路径所中断的操作。 常用原子操作函数举例: atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0 atom

yxf的博客 248

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

字符设备驱动

第001节_字符设备驱动程序之概念介绍 uboot的目的是启动内核。 内核的目的是启动应用。 应用程序涉及到很多文件操作、硬件操作(点灯、获取按键值等)。对于写应用程序的人不应该涉及到硬件操作。在应用层有:open、read、write等操作 ...

a1014388280的博客 423

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

linux字符驱动非阻塞,字符设备驱动程序(十)阻塞与非阻塞操作按键

#include "linux/module.h"//编写驱动程序时,双引号要改成尖括号#include "linux/kernel.h"#include "linux/fs.h"#include "linux/init.h"#include "linux/delay.h"#include "linux/irq.h"#include "asm/uaccess.h"#include "asm/irq...

weixin_39987211的博客 237

linux. qt信号崩溃,【创龙AM4379 Cortex-A9试用体验】之I/O中断异步通知驱动程序+QT捕获Linux系统信号+测试信号通知...

2.驱动程序安装字符设备驱动程序开发流程开发。2.1资源定义定义按键I/O端口号、I/O中断号,以及字符设备的主设备号变量:#define GPIO_KEY1_PIN_NUM (3*32 + 14) /*gpio 3_14 */#define GPIO_KEY2_PIN_NUM (3*32 + 16) /*gpio 3_16 */#define GPIO_KEY3_PIN_NUM (4*32 + ...

weixin_42431713的博客 423

Linux阻塞与非阻塞IO机制及select/poll内核实现

阻塞IO与非阻塞IO是Linux系统中进程与设备交互的基础同步范式,其本质在于内核对进程状态(如TASK_INTERRUPTIBLE)的调度控制与等待队列(Wait Queue)的协同管理。阻塞模式通过睡眠-唤醒机制避免忙等待,提升CPU利用率;非阻塞模式则以-EAGAIN错误码即时反馈,将事件决策权交予用户空间。技术价值体现在高并发服务(如Nginx)、实时嵌入式驱动及POSIX兼容性保障上。典型应用场景包括串口数据接收、ADC采样等待、多设备轮询监控等。本文深入解析open()标志位切换、select(

weixin_33239721的博客 51

设备驱动

1- 字符设备驱动框架: 2- LED驱动 3- 按键驱动 自己写中断方式按键驱动程序(详解) Linux驱动调试时的一些技巧 exec 5</dev/button 完整的按键驱动程序--转韦东山跟着韦东山老师学字符设备驱动之查询方式的按键驱动程序分析 韦东山第12课-字符设备、中断方式查询驱动 ...

u010743406的博客 138

字符设备驱动学习笔记---并发

linux中处理并发的几种解决方法:信号量和互斥体 自旋锁 原子操作      a 原子操作:    atomic_t 定义在中    常用操作:     void atomic_set(atomic_t *v,int i);     atomic_t v=ATMIC_INIT(0);//初始化原子变量         int atomic_read(atomic_t *v);/

retacn_yue的专栏 905

linux驱动

初级驱动1.模块的基本使用2.字符设备的注册3.设备号分配4.创建类5.创建设备结点6.内存映射7.改写write函数8.硬件控制9.GPIP函数10.内存读写函数11.注销12.将所有变量封装一起13.使用ioctl替换write14. Linux内核中file cdev inode之间的关系15.字符设备驱动开发步骤总结16.杂项设备驱动的简单写法17.中断使用18.中断下半部19.io非阻塞方式20.poll多路复用21.异步通知 1.模块的基本使用 1.驱动模块的入口和出口函数 stati

qq_43165445的博客 381

字符设备驱动程序——定时器按键消抖

实现方式 需要设置的结构体 struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; struct tvec_base *base; … } 1、static struct timer_list buttons_tim...

yxf的博客 390

韦东山第一二期衔接课程内容概要

韦东山第一二期衔接课程内容概要1 u-boot分析之编译体验2 u-boot分析之Makefile结构分析 1 u-boot分析之编译体验 通过分析u-boot的makefile去分析其代码结构 2 u-boot分析之Makefile结构分析

zwg的博客 2928

Linux字符设备驱动程序总结(一)

在这之前我们已经把字符设备驱动框架和模型,包括一些机制我们都讲的非常清楚了,在这里我在给大家总结一下。 首先大家理解几个概念,像物理地址和虚拟地址,IO映射和内存映射,操作IO我总结了有以下三种方法: 第一种方法:用的时候加上 *volatile unsigned long *adccon=NULL;volatile unsigned long *...

weixin_34174105的博客 491
上一篇: 最小根文件系统的构建(基于JZ2440)
下一篇: Socket网络编程基础与实例
so恒久
博客等级 码龄7年 1109粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值