14.2 并发与竞争实验
一、原子操作实验
这节使用原子操作来实现对 LED 设备的互斥访问,也就是只有一个应用程序能使用 LED。
1.1 实验程序编写
因为是 12 章已经修改了设备树,所以这里暂时不用修改。
在 /linux/atk-mpl/Drivers 该目录下创建 7_atomic 子目录,并且把 5_gpioled 里面的 gpioled.c 文件复制到 7_atomic 子目录下并重命名为 atomic.c,还在改子目录下创建 Vscode 工作区。首先先编写 atomic.c 程序:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号atomic_t lock; // 原子变量
};struct gpioled_dev gpioled; /* led设备 *//** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{/* 通过判断原子变量的值来检查LED有没有被别的应用使用 */if (!atomic_dec_and_test(&gpioled.lock)) // 给lock减1,如果结果为0返回真否则为假,这里用了取反,所以这里是不为0为真,这里只有1和0,减一说明要么0要么-1,这里判断lock为-1才往下走{ // 相反,lock为1,减一后为0,条件不满足不执行,这就是判断依据atomic_inc(&gpioled.lock); // 给lock加1 /* 小于0的话就加1,使其原子变量等于0 */retrun -EBUSY; /* LED被使用,返回忙 */}filp->private_data = &gpioled; /* 设置私有数据 */return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue = copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据,实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data; // 新加的,这里把私有数据给dev/* 关闭驱动文件的时候释放原子变量 */atomic_inc(&dev->lock);return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};/** @description : 驱动出口函数* @param : 无* @return : 无*/
static int __init led_init(void)
{int ret = 0;const char *str;// 1、初始化原子变量gpioled.lock = (atomic_t)ATOMIC_INIT(0);// 2、原子变量初始化为1atomic_set(&gpioled.lock, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(gpioled.nd, "status", &str); // 获取状态是否是"okay"if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(gpioled.nd, "compatible", &str);if(ret < 0) {printk("gpioled: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,led")) {printk("gpioled: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret = gpio_request(gpioled.led_gpio, "LED-GPIO"); // 这里设备树已经改成了led-gpio=<&gpioi 0 GPIO_ACTIVE_LOW>if (ret) {printk(KERN_ERR "gpioled: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);goto free_gpio;}gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(&gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
其次编写 atomicApp.c 测试文件:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 0
#define LEDON 1/** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char cnt = 0;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开led驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 *//* 向/dev/gpioled文件写入数据 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}/* 模拟占用25S LED */while(1) {sleep(5);cnt++;printf("App running times:%d\r\n", cnt);if(cnt >= 5) break;}printf("App running finished!");retvalue = close(fd); /* 关闭文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}
1.2 运行测试
编写 Makefile 文件:
KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31 # Linux内核源码路径
CURRENT_PATH := $(shell pwd)obj-m := atomic.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译 Makefile 文件得到 atomic.ko 文件。
make
编译 atomicApp.c 文件得到 atomicApp 文件。
arm-none-linux-gnueabihf-gcc atomicApp.c -o atomicApp
将这两个文件拷贝:
sudo cp atomicApp atomic.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/
开启开发板,输入命令加载 atomic.ko 驱动:
depmod
modprobe atomic.ko
输入命令开启 LED,并且每隔 5s 都会输出 App running times:
./atomicApp /dev/gpioled 1& # “&”表示在后台运行 atomicApp 这个软件
当在运行过程中输入以下命令的时候:
/atomicApp /dev/gpioled 0 # 关闭 LED 灯
打开 /dev/gpioled 失败,原因是 atomicApp 软件正在占用 /dev/gpioled,如果再次运行 atomicApp 软件去操作/dev/gpioled 肯定会失败。必须等待 atomicApp运行结束,也就是25S结束以后其他软件才能去操作/dev/gpioled。这个就是采用原子变量实现一次只能有一个应用程序访问 LED 灯。
最后卸载驱动:
rmmod atomic.ko
二、自旋锁实验
上节是使用原子操作实现一个应用程序访问 LED,这次换成自旋锁实现。
首先先注意自旋锁使用事项:
① 自旋锁保护的临界区尽可能的短。使用一个变量来表示设备的使用情况,如果设备被使用了那么变量就加一,设备被释放以后变量就减 1,我们只需要使用自旋锁保护这个变量即可。
② 考虑驱动兼容性,选择合理的 API 函数。
2.1 实验程序编写
不用修改设备树。
把上一节的 atomic.c、Makefiel、atomicApp.c 复制到新的子目录 8_spinlock 中,并把 atomic 相关的重命名为 spinlock,首先修改 spinlock.c 文件:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号int dev_stats; // 设备使用状态 0设备未使用 >0设备使用spinlock_t lock; // 自旋锁
};struct gpioled_dev gpioled; /* led设备 *//** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{unsigned long flags; // 中断状态变量filp->private_data = &gpioled; /* 设置私有数据 */spin_lock_irqsave(&gpioled.lock, flags); // 保存中断状态,禁止本地中断,并获取自旋锁;这里为什么没有使用spin_lock就是考虑到兼容性if (gpioled.dev_stats) // 如果设备被使用{spin_unlock_irqrestore(&gpioled.lock, flags); // 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁return -EBUSY;}gpioled.dev_stats++; // 如果设备没有使用,就让stats > 0使其使用变为使用中spin_unlock_irqrestore(&gpioled.lock, flags); // 解锁return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue = copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据,实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{unsigned long flags;struct gpioled_dev *dev = filp->private_data; // 新加的,这里把私有数据给dev/* 关闭驱动文件的时候将dev_stats减1 */spin_lock_irqsave(&dev->lock, flags); // 上锁if (dev->dev_stats) // dev_stats 为1成立,设备使用中{dev->dev_stats --; // dev_stats 为0,释放设备}spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */return 0;
}
// 这里有个疑惑为什么oepn和release都要有上锁和解锁,是因为确保在同一时间只有一个进程能改变dev_stats的值,可以防止竞争的发生/* 设备操作函数 */
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};/** @description : 驱动出口函数* @param : 无* @return : 无*/
static int __init led_init(void)
{int ret = 0;const char *str;// 自旋锁初始化spin_lock_init(&gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(gpioled.nd, "status", &str); // 获取状态是否是"okay"if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(gpioled.nd, "compatible", &str);if(ret < 0) {printk("gpioled: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,led")) {printk("gpioled: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret = gpio_request(gpioled.led_gpio, "LED-GPIO"); // 这里设备树已经改成了led-gpio=<&gpioi 0 GPIO_ACTIVE_LOW>if (ret) {printk(KERN_ERR "gpioled: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);goto free_gpio;}gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(&gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
测试 APP 跟上节一样,把名字变成 spinlockApp.c 即可。
2.2 运行测试
修改 Makefile 中的
,改为 spinlock.o 即可。
编译 spinlock.c 文件:
make
编译 spinlockApp.c 文件:
arm-none-linux-gnueabihf-gcc spinlockApp.c -o spinlockApp
把以上两个文件复制:
sudo cp spinlockApp spinlock.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/
开启开发板,输入以下命令:
cd lib/modules/5.4.31/
depmod
modprobe spinlock.ko
使用 spinlockApp 进行测试驱动:
./spinlockApp /dev/gpioled 1& // 打开 LED 灯
./spinlockApp /dev/gpioled 0 // 关闭 LED 灯
驱动正常工作的话不会立马关闭 LED,会提示 file /dev/gpioled open failed!,必须等待第一个 spinlock App 软件运行完成才可以关闭。
卸载驱动:
rmmod spinlock.ko
三、信号量实验
使用信号量来实现只能有一个应用程序访问 LED,因为信号量可以导致休眠,所以信号量保护的临界区没有运行时间限制,就可以在 open 函数申请信号量,在 release 函数中释放信号量。
3.1 实验程序编写
不用修改设备树。
新建 9_semaphore 文件夹,并按上一小节这样操作,只需要把 spinlock 改为 semaphore 即可。修改 semaphore.c 文件:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号struct semaphore sem; // 信号量
};struct gpioled_dev gpioled; /* led设备 *//** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &gpioled; /* 设置私有数据 */if (down_interruptible(&gpioled.sem)); // 获取信号量,进入休眠以后是可以被信号打断的,这时候count为0{return -ERESTARTSYS;}/* down(&gpioled.sem); // 获取信号量,不能被信号打断
*/return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue = copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据,实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data; // 这里把私有数据给devup(&dev->sem); // 释放信号量,信号量count值加1return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};/** @description : 驱动出口函数* @param : 无* @return : 无*/
static int __init led_init(void)
{int ret = 0;const char *str;/* 初始化信号量 */sema_init(&gpioled.sem, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(gpioled.nd, "status", &str); // 获取状态是否是"okay"if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(gpioled.nd, "compatible", &str);if(ret < 0) {printk("gpioled: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,led")) {printk("gpioled: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret = gpio_request(gpioled.led_gpio, "LED-GPIO"); // 这里设备树已经改成了led-gpio=<&gpioi 0 GPIO_ACTIVE_LOW>if (ret) {printk(KERN_ERR "gpioled: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);goto free_gpio;}gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(&gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
当信号量 sem 为 1 的时候表示 LED 灯还没有被使用,如果应用程序 A 要使用LED 灯,先调用 open 函数打开/dev/gpioled,这个时候会获取信号量 sem,获取成功以后 sem 的值减 1 变为 0。如果此时应用程序 B 也要使用 LED 灯,调用 open 函数打开/dev/gpioled 就会因为信号量无效(值为 0)而进入休眠状态。当应用程序 A 运行完毕,调用 close 函数关闭/dev/gpioled的时候就会释放信号量 sem,此时信号量 sem 的值就会加 1,变为 1。信号量 sem 再次有效,表示其他应用程序可以使用 LED 灯了,此时在休眠状态的应用程序 B 就会获取到信号量 sem,获取成功以后就开始使用 LED 灯。
3.2 运行测试
修改 Makefile 文件,跟上节一样,只不过改为 obj-m := semaphore.o。
编译 semaphore.c 文件:
make
编译 semaphoreApp.c 文件:
arm-none-linux-gnueabihf-gcc semaphoreApp.c -o semaphoreApp
最后将以上两个文件复制:
sudo cp semaphoreApp semaphore.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/
开启开发板,加载驱动:
depmod
modprobe semaphore.ko
驱动加载完成使用 semaphoreApp 测试驱动:
./semaphoreApp /dev/gpioled 1& # 打开 LED 灯
./semaphoreApp /dev/gpioled 0& # 关闭 LED 灯
首先,第一条命令先获取信号量,因此可以操作 LED,所以这时候开发板上面的 LED 是亮着的。第二条命令因为也想获得 LED 使用权,但是被第一条命令抢先了,所以第二条命令就休眠,等到第一条命令完成的时候,释放信号量,第二条命令才能拥有 LED 使用权,这时候发现开发板的 LED 是灭的。总共开发板前 25s 亮,后 25s 灭。
卸载驱动:
rmmod semaphore.ko
四、互斥体实验
其实最适合互斥的就是互斥体 mutex。怎么感觉在说废话。
4.1 实验程序编写
不用修改设备树。
跟上节一样的操作,全部改为 mutex。对了,每次记得添加头文件路径,修改 mutex.c 文件:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 */struct device_node *nd; /* 设备节点 */int led_gpio; /* led所使用的GPIO编号 */ // 此成员变量保存 LED 等所使用的 GPIO 编号struct mutex lock; // 定义互斥体
};struct gpioled_dev gpioled; /* led设备 *//** @description : 打开设备* @param - inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &gpioled; /* 设置私有数据 */if (mutex_lock_interruptible(&gpioled.lock)) // 获取互斥体,可以被信号打断{return -ERESTARTSYS;}/* mutex_lock(&gpioled.lock); // 获取信号量,不能被信号打断
*/return 0;
}/** @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data; // 通过读取 filp 的 private_data 成员变量来得到设备结构体变量retvalue = copy_from_user(databuf, buf, cnt); /* 接收APP发送过来的数据 */if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 *//* 调用 gpio_set_value 函数来向 GPIO 写入数据,实现开/关 LED 的效果。不需要直接操作相应的寄存器 */if(ledstat == LEDON) { gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data; // 这里把私有数据给devmutex_unlock(&dev->lock); // 释放互斥锁return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};/** @description : 驱动出口函数* @param : 无* @return : 无*/
static int __init led_init(void)
{int ret = 0;const char *str;/* 初始化互斥体 */mutex_init(&gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(gpioled.nd, "status", &str); // 获取状态是否是"okay"if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(gpioled.nd, "compatible", &str);if(ret < 0) {printk("gpioled: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,led")) {printk("gpioled: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 获取 LED 所使用的 LED 编号。相当于将 gpioled 节点中的“led-gpio”属性值转换为对应的 LED 编号if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 5.向gpio子系统申请使用GPIO */ret = gpio_request(gpioled.led_gpio, "LED-GPIO"); // 这里设备树已经改成了led-gpio=<&gpioi 0 GPIO_ACTIVE_LOW>if (ret) {printk(KERN_ERR "gpioled: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) { /* 定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);goto free_gpio;}} else { /* 没有定义设备号 */ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);goto free_gpio;}gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor); /* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {goto del_cdev;}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(gpioled.class);
del_cdev:cdev_del(&gpioled.cdev);
del_unregister:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:gpio_free(gpioled.led_gpio);return -EIO;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/* 删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */class_destroy(gpioled.class);/* 注销类 */gpio_free(gpioled.led_gpio); /* 释放GPIO */
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
4.2 运行测试
修改 Makefile ,mutex.o。
编译 mutex.c 文件和 mutexApp.c 文件:
make
arm-none-linux-gnueabihf-gcc mutexApp.c -o mutexApp
上面两个文件复制到:
sudo cp mutexApp mutex.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/
加载驱动:
depmod
modprobe mutex.ko
使用 mutexApp 测试驱动:
./mutexApp /dev/gpioled 1& # 打开 LED 灯
./mutexApp /dev/gpioled 0& # 关闭 LED 灯
跟信号量的效果一样。卸载驱动:
rmmod mutex.ko
相关文章:

14.2 并发与竞争实验
一、原子操作实验 这节使用原子操作来实现对 LED 设备的互斥访问,也就是只有一个应用程序能使用 LED。 1.1 实验程序编写 因为是 12 章已经修改了设备树,所以这里暂时不用修改。 在 /linux/atk-mpl/Drivers 该目录下创建 7_atomic 子目录,并且…...

【MediaTek】T750实现Host 网络和Guest 网络隔离以及各个连接终端间隔离功能
T750 WiFi WiFi芯片MT7915AN Wi-Fi 标准IEEE 802.11a/b/g/n/ac/ax支持的速率802.11ax: 4 到 2400 Mbps802.11ac: 6.5 到 1732 Mbps802.11n: 6.5 到 600 Mbps802.11a/g:6 到 54 Mbps802.11b: 1 到 11 Mbps支持的信道2.4 GHz:1-135 GHz:36-64、100-144 和 149-165多输入多输…...

数字滤波器之高通滤波器设计
文章来源地址:https://www.yii666.com/blog/393376.html 通过在Z平面放置零极点的来设计数字滤波器 要求:设计一款高通滤波器,用在音频信号处理过程中,滤掉100Hz以下的信号。 实现方法:通过在Z平面放置零极点的来设…...

【leetcode】58.最后一个单词的长度
题目 最后一个单词的长度 给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1: 输入:s “Hello World”…...

用Java(C语言也可以看)实现冒泡排序和折半查找(详细过程图)+逆序数组
目录 一、冒泡排序 1.冒泡排序介绍 2.排序的思路 3.完整代码 二、折半查找 1.折半查找介绍 2.查找的思路 3.完整代码 三、逆序数组 1.逆序思路 2..完整代码 一、冒泡排序 冒泡排序是众多排序的一种,无论在C语言或者Java中都很常见,后续在数据…...

antd本地上传excel文件并读取文件的数据转为json
1.写一个上传 这里直接用upload组件即可 <Upload {...uploadProps} maxCount{1} accept{".xlsx"}><Button icon{<UploadOutlined />}>{${formatMessage({id: clk_upload}, {file: formatMessage({id: excel_file})})}}</Button></Uploa…...

BI数据可视化:不要重复做报表,只需更新数据
BI数据可视化是一种将大量数据转化为视觉形式的过程,使得用户可以更容易地理解和分析数据。然而,传统的报表制作过程往往需要手动操作,不仅耗时还容易出错。为了解决这个问题,BI数据可视化工具通常会提供一些自动化的数据更新功能…...

fiddler抓包拦截请求转发到其他地址
使用Fiddler拦截请求转发到指定地址方便于本地调试,不需要进行打包切换地址,可以加快问题的确定修复效果 内容: 1:首先给app进行设置代理抓包内容,给进行 https://blog.csdn.net/qq_43717814/article/details/84317038…...

【Shell编程】| if 判断
最近在编写一些测试程序的时候,对if的使用较为片面,很多小的功能都需要去各个地方百度查询,极为不便,因此也想着空闲时候,对if进行详细总结,一来加深印象,二来是为了打造一个if语句的最详细的使…...

Java手动引入Maven依赖的Jar包
🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开心好久好久😎 📚系列专栏:Java全栈,…...

计算机毕设 基于大数据的社交平台数据爬虫舆情分析可视化系统
文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕…...

conda取消自动进入base环境
安装conda后取消命令行前出现的base,则默认进入了conda环境,如果想取消每次启动自动激活conda的基础环境。 方法一 每次在命令行通过conda deactivate退出base环境回到系统自带的环境 如果再进入的话: conda deactivate 方法二 1&#…...

【文生图】Stable Diffusion XL 1.0模型Full Fine-tuning指南(U-Net全参微调)
文章目录 前言重要教程链接以海报生成微调为例总体流程数据获取POSTER-TEXTAutoPosterCGL-DatasetPKU PosterLayoutPosterT80KMovie & TV Series & Anime Posters 数据清洗与标注模型训练模型评估生成图片样例宠物包商品海报护肤精华商品海报 一些TipsMata:…...

STM32笔记—DMA
目录 一、DMA简介 二、DMA主要特性 三、DMA框图 3.1 DMA处理 3.2 仲裁器 3.3 DMA通道 扩展: 断言: 枚举: 3.4 可编程的数据传输宽度、对齐方式和数据大小端 3.5 DMA请求映像 四、DMA基本结构 4.1 DMA_Init配置 4.2 实现DMAADC扫描模式 实现要求…...

机器学习概论
一、机器学习概述 1、机器学习与人工智能、深度学习的关系 人工智能:机器展现的人类智能机器学习:计算机利用已有的数据(经验),得出了某种模型,并利用此模型预测未来的一种方法。深度学习:实现机器学习的一种技术 2…...

卡尔曼家族从零解剖-(04)贝叶斯滤波→细节讨论,逻辑梳理,批量优化
讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的 卡尔曼家族从零解剖 链接 :卡尔曼家族从零解剖-(00)目录最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/133846882 文末正下方中心提供了本人 联系…...

小菜React
1、Unterminated regular expression literal, 对于函数就写.ts,有dom元素就写.tsx 2、 The requested module /src/components/setup.tsx?t1699255799463 does not provide an export named Father export default useStore默认导出的钩子,组件引入的…...

新手用mac电脑,对文件的疑问和gpt回应
macOs系统安装软件的疑问 所有问题mac系统文件结构我用mac安装软件,不用像windows一样创建文件夹吗只能安装到Applications文件夹吗安装程序的指南和提供的安装选项是什么软件安装在Applications下的/appName文件夹,它的所有数据都会在该文件夹吗如果卸载…...

LeetCode|动态规划|392. 判断子序列、115. 不同的子序列、 583. 两个字符串的删除操作
目录 一、392. 判断子序列 1.题目描述 2.解题思路 3.代码实现(双指针解法) 4.代码实现(动态规划解法) 二、115. 不同的子序列 1.题目描述 2.解题思路 3.代码实现(C语言版本) 4.代码实现(C版本) …...

vscode 阅读 android以及kernel 源码
在Ubuntu系统中安装vscode 参考文档: https://blog.csdn.net/m0_57368670/article/details/127184424 1, 下载vscode https://code.visualstudio.com 2, 安装vscode $ sudo dpkg -i code_1.78.1-1683194560_amd64.deb 3, 打开vscode $ code vscode 阅读 android…...

Intel oneAPI笔记(3)--jupyter官方文档(SYCL Program Structure)学习笔记
前言 本文是对jupyterlab中oneAPI_Essentials/02_SYCL_Program_Structure文档的学习记录,包含对Device Selector、Data Parallel Kernel、Host Accessor、Buffer Destruction、的介绍,最后还有一个小关于向量(Vector)加法的实例 …...

verilog——移位寄存器
在Verilog中,你可以使用移位寄存器来实现数据的移位操作。移位寄存器是一种常用的数字电路,用于将数据向左或向右移动一个或多个位置。这在数字信号处理、通信系统和其他应用中非常有用。以下是一个使用Verilog实现的简单移位寄存器的示例: m…...

C++11 多线程学习笔记
1. thread — 线程篇 所需头文件:<thread> 1.1 构造函数 // 1 默认构造函数 thread() noexcept; // 2 移动构造函数,把other的所有权转移给新的thread对象,之后 other 不再表示执行线程。 thread( thread&& other ) noex…...

nn.embedding函数详解(pytorch)
提示:文章附有源码!!! 文章目录 前言一、nn.embedding函数解释二、nn.embedding函数使用方法四、模型训练与预测的权重变化探讨 前言 最近发现prompt工程(如sam模型),也有transform的detr模型等都使用了nn.Embedding函…...

gitee.com[0: xxx.xx.xxx.xx]: errno=Unknown error
git在提交或拉取代码的时候,遇到以下报错信息: Unable to connect to gitee.com[0: xxx.xx.xxx.xx]: errnoUnknown error 解决问题步骤: 1、找到自己的电脑上的git用户配置文件 文件位置位于:C:\Users\用户名\.gitconfig 比如我…...

bug: https://aip.baidubce.com/oauth/2.0/token报错blocked by CORS policy
还是跟以前一样,我们先看报错点:(注意小编这里是H5解决跨域的,不过解决跨域的原理都差不多) Access to XMLHttpRequest at https://aip.baidubce.com/oauth/2.0/token from origin http://localhost:8000 has been blo…...

简单工厂VS工厂方法
工厂方法模式–制造细节无需知 前面介绍过简单工厂模式,简单工厂模式只是最基本的创建实例相关的设计模式。在真实情况下,有更多复杂的情况需要处理。简单工厂生成实例的类,知道了太多的细节,这就导致这个类很容易出现难维护、灵…...

使用VSCODE链接Anaconda
打代码还是在VSCODE里得劲 所以得想个办法在VSCODE里运行py文件 一开始在插件商店寻找插件 但是没有发现什么有效果的 幸运的是VSCODE支持自己选择Python的编译器 打开VSCODE 按住CtrlShiftP 输入Select Interpreter 如果电脑已经安装上了Python的环境 VSCODE会默认选择普通…...

Mysql数据库 9.SQL语言 查询语句 连接查询、子查询
连接查询 通过查询多张表,用连接查询进行多表联合查询 关键字:inner join 内连接 left join 左连接 right join 右连接 数据准备 创建新的数据库:create database 数据库名; create database db_test2; 使用数据库:use 数据…...

二叉树按二叉链表形式存储,试编写一个判别给定二叉树是否是完全二叉树的算法
完全二叉树:就是每层横着划过去是连起来的,中间不会断开 比如下面的左图就是完全二叉树 再比如下面的右图就是非完全二叉树 那我们可以采用层序遍历的方法,借助一个辅助队列 当辅助队列不空的时候,出队头元素,入队头…...