19 异步通知

一、异步通知

1. 异步通知简介

  阻塞和非阻塞两种方式都是需要应用程序去主动查询设备的使用情况。

  异步通知类似于驱动可以主动报告自己可以访问,应用程序获取信号后会从驱动设备中读取或写入数据。

  异步通知最核心的就是信号

#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

  其中 9 和 19 是不能被忽略的,这些信号相当于中断号,不同的中断号代表不同的中断,不同的中断又可以实现不同的功能。

  使用中断的时候需要设置中断处理函数,那么应用程序中使用了信号,也要有信号处理函数,在应用程序中使用 signal 函数来设置信号的处理函数:

/*
 * @description : 设置指定信号的处理函数
 * @param - signum : 要设置处理函数的信号
 * @param - handler : 信号的处理函数
 * @return : 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR
 */
sighandler_t signal(int signum, sighandler_t handler);

  这里的信号处理原型为:

typedef void (*sighandler_t)(int)

  之前使用的 kill -9 PID就是指定进程发送  SIGKILL 这个信号。当我们按下 CTRL + C 的时候,会向当前正在占用终端的应用程序发送 SIGINT(2)信号,最终中断终端。

  在 linux/atk-mpl 文件夹下,创建 signaltest.c,并输入以下代码:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

/* 信号处理函数 */
void signalint_handler(int num)
{
    printf("\r\nSIGINT signal!\r\n");
    exit(0);
}

int main(void)
{
    signal(SIGINT, signalint_handler);
    while(1);
    return 0;
}

/* 
 总体流程:首先自己设置信号处理函数,之后应用程序开启,按下CTRL+C向signaltest发送SIGINT信号以后
 signalint_handler函数就会执行,此函数会先输出SIGINT signal!后,exit函数会关闭signaltest的应用程序。
 */

  输入命令编译 signaltest.c:

gcc signaltest.c -o signaltest

   输入命令执行应用程序 signaltest:

./signaltest

   按下 CTRL+ C后,可以看到:

2. 驱动中的信号处理

① fasync_struct 结构体

  如果在驱动中使用信号,首先先定义一个fasync_struct结构体变量,一般将这个结构体放在 xxx_dev 结构体中,例如:

/* key设备结构体 */
struct key_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int key_gpio;			/* key所使用的GPIO编号		*/
	struct timer_list timer;			/* 按键值 		*/
	int irq_num;			/* 中断号 		*/
	
	atomic_t status;   		/* 按键状态 */
	wait_queue_head_t r_wait;	/* 读等待队列头 */
        struct fasync_struct *async_queue;    /* fasync_struct结构体 */
};

② fasync 函数

  函数负责将用户空间应用程序的进程 ID 添加到驱动程序相应文件的异步通知队列中。

  如果要使用异步通知,需要在驱动程序里实现 file_operation 操作集中的 fasync函数:

int (*fasync) (int fd, struct file *filp, int on)

  驱动中的 fasync 函数示例如下:

struct xxx_dev {
    ......
    struct fasync_struct *async_queue; /* 异步相关结构体 */
};

static int xxx_fasync(int fd, struct file *filp, int on)    // fasync 函数
{
    struct xxx_dev *dev = (xxx_dev)filp->private_data;    // 私有数据

    if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)    // 这里的fasync_helper其实是被fasync调用的
        return -EIO;
    return 0;
}

static struct file_operations xxx_ops = {
    ......
    .fasync = xxx_fasync,    // 只要是 file_operations里的函数,都要这样的形势走一遍
    ......
};

  如果需要关闭驱动文件,那么需要在 release 函数里面释放 fasync_struct,示例如下:

static int xxx_release(struct inode *inode, struct file *filp)
{
    return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}
static struct file_operations xxx_ops = {
    ......
    .release = xxx_release,        // 只要用到了file_operations就要这样
};

③ kill_fasync 函数

/*
 * @description : 当设备可以访问的时候,驱动程序需要向应用程序发出信号,此函数负责发送指定的信号
 * @param - fp : 要操作的 fasync_struct
 * @param - sig : 要发送的信号
 * @param - band : 可读时设置为 POLL_IN,可写时设置为 POLL_OUT
 * @return : 无
 */
void kill_fasync(struct fasync_struct **fp, int sig, int band);

3. 应用程序对异步通知的处理

  总共包含三步:

① 注册信号处理函数

  这里的处理函数需要自己设置,并且最后传给 signal 函数。

② 将本应用程序的进程号告诉内核

  使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。 

③ 开启异步通知

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

  重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。 

  当用户空间应用程序调用 fcntl 系统调用并设置 F_SETFL 命令以及 FASYNC 标志时,内核会调用驱动程序中的 fasync 函数。

二、程序编写

  在 /linux/atk-mpl/Drivers 文件夹下创建 16_asynchronization 文件夹,并创建 asynchronization.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 <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fcntl.h>

#define KEY_CNT			1		/* 设备号个数 	*/
#define KEY_NAME		"key"	/* 名字 		*/

/* 定义按键状态 */
enum key_status {
    KEY_PRESS = 0,      // 按键按下
    KEY_RELEASE,        // 按键松开
    KEY_KEEP,           // 按键状态保持
};

/* key设备结构体 */
struct key_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int key_gpio;			/* key所使用的GPIO编号		*/
	struct timer_list timer;			/* 按键值 		*/
	int irq_num;			/* 中断号 		*/	
	atomic_t status;   		/* 按键状态 */
	wait_queue_head_t r_wait;	/* 读等待队列头 */
	struct fasync_struct *async_queue;	/* fasync_struct结构体 */
};

static struct key_dev key;          /* 按键设备 */

static irqreturn_t key_interrupt(int irq, void *dev_id)		// 中断处理函数
{
	/* 按键防抖处理,开启定时器延时15ms */
	mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

/*
 * @description	: 初始化按键IO,open函数打开驱动的时候
 * 				  初始化按键所使用的GPIO引脚。
 * @param 		: 无
 * @return 		: 无
 */
static int key_parse_dt(void)
{
	int ret;
	const char *str;
	
	/* 设置LED所使用的GPIO */
	/* 1、获取设备节点:key */
	key.nd = of_find_node_by_path("/key");
	if(key.nd == NULL) {
		printk("key node not find!\r\n");
		return -EINVAL;
	}

	/* 2.读取status属性 */
	ret = of_property_read_string(key.nd, "status", &str);
	if(ret < 0) 
	    return -EINVAL;

	if (strcmp(str, "okay"))
        return -EINVAL;
    
	/* 3、获取compatible属性值并进行匹配 */
	ret = of_property_read_string(key.nd, "compatible", &str);
	if(ret < 0) {
		printk("key: Failed to get compatible property\n");
		return -EINVAL;
	}

    if (strcmp(str, "alientek,key")) {
        printk("key: Compatible match failed\n");
        return -EINVAL;
    }

	/* 4、 获取设备树中的gpio属性,得到KEY0所使用的KYE编号 */
	key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);
	if(key.key_gpio < 0) {
		printk("can't get key-gpio");
		return -EINVAL;
	}

    /* 5 、获取GPIO对应的中断号 */
    key.irq_num = irq_of_parse_and_map(key.nd, 0);
    if(!key.irq_num){
        return -EINVAL;
    }

	printk("key-gpio num = %d\r\n", key.key_gpio);
	return 0;
}

static int key_gpio_init(void)		// gpio初始化
{
	int ret;
    unsigned long irq_flags;
	
	ret = gpio_request(key.key_gpio, "KEY0");		// 申请gpio
    if (ret) {
        printk(KERN_ERR "key: Failed to request key-gpio\n");
        return ret;
	}	
	
	/* 将GPIO设置为输入模式 */
    gpio_direction_input(key.key_gpio);

   /* 获取设备树中指定的中断触发类型 */
	irq_flags = irq_get_trigger_type(key.irq_num);
	if (IRQF_TRIGGER_NONE == irq_flags)
		irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
		
	/* 申请中断 */
	ret = request_irq(key.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);
	if (ret) {
        gpio_free(key.key_gpio);
        return ret;
    }

	return 0;
}

static void key_timer_function(struct timer_list *arg)		// 定时器处理函数
{
    static int last_val = 1;
    int current_val;

    /* 读取按键值并判断按键当前状态 */
    current_val = gpio_get_value(key.key_gpio);
    if (0 == current_val && last_val){
        atomic_set(&key.status, KEY_PRESS);	// 按下
		wake_up_interruptible(&key.r_wait);	// 唤醒r_wait队列头中的所有队列
		if(key.async_queue)
			kill_fasync(&key.async_queue, SIGIO, POLL_IN);	// 
	}
    else if (1 == current_val && !last_val) {
        atomic_set(&key.status, KEY_RELEASE);   // 松开
		wake_up_interruptible(&key.r_wait);	// 唤醒r_wait队列头中的所有队列
		if(key.async_queue)
			kill_fasync(&key.async_queue, SIGIO, POLL_IN);
	}
    else
        atomic_set(&key.status, KEY_KEEP);              // 状态保持

    last_val = current_val;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int key_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * @description     : 从设备读取数据 
 * @param – filp    : 要打开的设备文件(文件描述符)
 * @param – buf     : 返回给用户空间的数据缓冲区
 * @param – cnt     : 要读取的数据长度
 * @param – offt    : 相对于文件首地址的偏移
 * @return          : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key_read(struct file *filp, char __user *buf,
            size_t cnt, loff_t *offt)
{
    int ret;
	
	if (filp->f_flags & O_NONBLOCK) {	// 非阻塞方式访问
        if(KEY_KEEP == atomic_read(&key.status))
            return -EAGAIN;
    } else {							// 阻塞方式访问
	/* 加入等待队列,当有按键按下或松开动作发生时,才会被唤醒 */
	ret = wait_event_interruptible(key.r_wait, KEY_KEEP != atomic_read(&key.status));
	if(ret)
		return ret;
	}
    /* 将按键状态信息发送给应用程序 */
    ret = copy_to_user(buf, &key.status, sizeof(int));

    /* 状态重置 */
    atomic_set(&key.status, KEY_KEEP);

    return ret;
}

 /*
 * @description		: fasync函数,用于处理异步通知
 * @param – fd		: 文件描述符
 * @param – filp	: 要打开的设备文件(文件描述符)
 * @param – on		: 模式
 * @return			: 负数表示函数执行失败
 */
static int key_fasync(int fd, struct file *filp, int on)
{
	return fasync_helper(fd, filp, on, &key.async_queue);
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int key_release(struct inode *inode, struct file *filp)
{
	return key_fasync(-1, filp, 0);
}

/*
 * @description     : poll函数,用于处理非阻塞访问
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - wait    : 等待列表(poll_table)
 * @return          : 设备或者资源状态,
 */
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;

    poll_wait(filp, &key.r_wait, wait);

    if(KEY_KEEP != atomic_read(&key.status))	// 按键按下或松开动作发生
		mask = POLLIN | POLLRDNORM;	// 返回PLLIN

    return mask;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
	.write = key_write,
	.release = 	key_release,
	.poll = key_poll,
	.fasync	= key_fasync,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init mykey_init(void)
{
	int ret;
	
	/* 初始化等待队列头 */
	init_waitqueue_head(&key.r_wait);
	
	/* 初始化按键状态 */
	atomic_set(&key.status, KEY_KEEP);

	/* 设备树解析 */
	ret = key_parse_dt();
	if(ret)
		return ret;
		
	/* GPIO 中断初始化 */
	ret = key_gpio_init();
	if(ret)
		return ret;
		
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);	/* 申请设备号 */
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", KEY_NAME, ret);
		goto free_gpio;
	}
	
	/* 2、初始化cdev */
	key.cdev.owner = THIS_MODULE;
	cdev_init(&key.cdev, &key_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&key.cdev, key.devid, KEY_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4、创建类 */
	key.class = class_create(THIS_MODULE, KEY_NAME);
	if (IS_ERR(key.class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	key.device = device_create(key.class, NULL, key.devid, NULL, KEY_NAME);
	if (IS_ERR(key.device)) {
		goto destroy_class;
	}
	
	/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
	timer_setup(&key.timer, key_timer_function, 0);
	
	return 0;

destroy_class:
	device_destroy(key.class, key.devid);
del_cdev:
	cdev_del(&key.cdev);
del_unregister:
	unregister_chrdev_region(key.devid, KEY_CNT);
free_gpio:
	free_irq(key.irq_num, NULL);
	gpio_free(key.key_gpio);
	return -EIO;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit mykey_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&key.cdev);/*  删除cdev */
	unregister_chrdev_region(key.devid, KEY_CNT); /* 注销设备号 */
	del_timer_sync(&key.timer);		/* 删除timer */
	device_destroy(key.class, key.devid);/*注销设备 */
	class_destroy(key.class); 		/* 注销类 */
	free_irq(key.irq_num, NULL);	/* 释放中断 */
	gpio_free(key.key_gpio);		/* 释放IO */
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  编写 asynchronizationApp.c 文件:

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

static int fd;

/*
 * SIGIO信号处理函数
 * @param – signum		: 信号值
 * @return				: 无
 */
static void sigio_signal_func(int signum)
{
    unsigned int key_val = 0;

    read(fd, &key_val, sizeof(unsigned int));
    if (0 == key_val)
        printf("Key Press\n");
    else if (1 == key_val)
        printf("Key Release\n");

    printf("SIGIO signal!\r\n");
}


/*
 * @description		: main主程序
 * @param – argc		: argv数组元素个数
 * @param – argv		: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int flags = 0;

    /* 判断传参个数是否正确 */
    if(2 != argc) {
        printf("Usage:\n"
               "\t./asyncKeyApp /dev/key\n"
              );
        return -1;
    }

    /* 打开设备 */
    fd = open(argv[1], O_RDONLY | O_NONBLOCK);  // 非阻塞O_NONBLOCK
    if(0 > fd) {
        printf("ERROR: %s file open failed!\n", argv[1]);
        return -1;
    }

    /* 设置信号SIGIO的处理函数 */
    signal(SIGIO, sigio_signal_func);       // signal函数,用于设置信号的处理函数
    fcntl(fd, F_SETOWN, getpid());			// 将当前进程的进程号告诉给内核
    flags = fcntl(fd, F_GETFD);				// 获取当前的进程状态
    fcntl(fd, F_SETFL, flags | FASYNC);		// 设置进程启用异步通知功能


    /* 循环轮询读取按键数据 */
    while(1) {

        sleep(2);
    }

    /* 关闭设备 */
    close(fd);
    return 0;
}

三、运行测试

  首先先编写 Makefile 文件:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)

obj-m := asynchronization.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  之后编译 asynchronization.c 和 asynchronizationApp 文件:

make
arm-none-linux-gnueabihf-gcc asynchronizationApp.c -o asynchronization

  将编译好的 asynchronizationApp 和 asynchronization.ko 复制到:

sudo cp asynchronization asynchronization.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  开启开发板,输入以下命令:

cd lib/modules/5.4.31/
depmod
modprobe asynchronization.ko

   测试 App 应用程序:

./asynchronization /dev/key

  按下 Key0,则会出现:

  每当按下 KEY0 的时候,就会捕获到 SIGIO信号,并且按键获取成功。

  卸载驱动:rmmod asynchronization.ko

总结

  概念:

  其实异步通知就是让驱动设备空出来的时候发送一个信号给用户进程空间,让进程空间知道,哦原来可以使用这个设备了,一般搭配 非阻塞+异步通知使用,效果更佳。

  异步通知的使用方法需要掌握,他们分为驱动和用户进程空间:

  在内核空间中:① 定义 fasync_struct结构体;② 编写fasync函数;③ 最后当设备空出来的时候指定信号 kill_fasync

  在用户空间中:① 编写信号处理函数,并且最后把信号处理函数传给 signal函数;② 本程序进程号告诉内核 fcntl(fd, F_SETOWN, getpid());③ 开启异步通知:flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */ fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

  代码:其实跟上一节差不太多,在按键按下/松开的时候设置kill_fasync发送信号给用户空间。有个小tip,只要使用了file_operations函数里的函数,都要在 static struct file_operations key_fops = {...}这个函数下写下设备操作函数。测试代码中按照上述用户空间编写即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/134447.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C详细的字符串函数

但行前路&#xff0c;莫问归期 要注意的是&#xff0c;要使用下边所讲的函数要包含头文件<string.h> 文章目录 strlenstrcpystrncpy strcatstrncat strcmpstrncmp strstrstrtokstrerror字符串大小写转换struprstrlwr memcpymemmovememcmp strlen 求字符串的长度 函数参…

Typescript -尚硅谷

基础 1.ts是以js为基础构建的语言&#xff0c;是一个js的超集(对js进行了扩展)&#xff1b; 2.ts(type)最主要的功能是在js的基础上引入了类型的概念; Js的类型是只针对于值而言&#xff0c;ts的类型是针对于变量而言 Ts可以被编译成任意版本的js&#xff0c;从而进一步解决了…

MySQL Command Line Client 运行闪退问题解决,缺少my.ini文件

MySQL Command Line Client 运行闪退问题解决&#xff1a; 问题排查&#xff1a; 1.找到Command Line Client的路径位置&#xff0c;并查看属性&#xff0c;步骤截图&#xff1a; 查看属性&#xff1a; 查看属性中的目标路径&#xff1a; 2.进入属性中的目标路径&#xff0c;…

基于SSM+Vue的电子商城的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

记录--vue3 setup 中国省市区三级联动options最简洁写法,无需任何库

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 在写页面的时候&#xff0c;发现表单里面有一个省市区的 options 组件要写&#xff0c;因为表单很多地方都会用到这个地址选择&#xff0c;我便以为很简单嘛。 虽然很简单的一个功能&#xff0c;但是网…

C#中的扩展方法---Extension

C#中扩展方法是C# 3.0/.NET 3.x 新增特性&#xff0c;能够实现向现有类型中“添加”方法&#xff0c;以下主要介绍C#中扩展方法的声明及使用。 1、扩展方法的声明 扩展方法使能够向现有类型“添加”方法&#xff0c;而无需创建新的派生类型、重新编译或以其他方式修改原始类型…

安全通信网络(设备和技术注解)

网络安全等级保护相关标准参考《GB/T 22239-2019 网络安全等级保护基本要求》和《GB/T 28448-2019 网络安全等级保护测评要求》 密码应用安全性相关标准参考《GB/T 39786-2021 信息系统密码应用基本要求》和《GM/T 0115-2021 信息系统密码应用测评要求》 1网络架构 1.1保证网络…

开发知识点-Python

Python从小白到入土 python渗透测试安全工具开发锦集Python安全工具编程基础第一章 Python在网络安全中的应用第一节 Python黑客领域的现状第二节 我们可以用Python做什么第三节 第一章课程内容总结 第二章 python安全应用编程入门第一节 Python正则表达式第二节 Python Web编程…

虚幻引擎:如何进行关卡切换?无缝切换?

一丶非无缝切换 在切换的时候会先断开连接,等创建好后才会链接,造成体验差 蓝图中用到的节点是 Execute Console Command 二丶无缝切换 链接的时候不会断开连接,中间不会出现卡顿,携带数据转换地图 1.需要在gamemode里面开启无缝漫游,开启之后使用上面的切换方式就可以做到无缝…

VueRequest——管理请求状态库

文章目录 前言一、为什么选择 VueRequest&#xff1f;二、使用步骤1.安装2.用例 前言 VueRequest——开发文档 VueReques——GitHub地址 在以往的业务项目中&#xff0c;我们经常会被 loading 状态的管理、请求的节流防抖、接口数据的缓存、分页等重复的功能实现所困扰。每次开…

选购Ipad以及投入学习生产(玩耍)

图片 from Pinterest Ipad Air 5 趁着2023年暑期教育优惠购入&#xff0c;Ipad Air 5 64G版本&#xff0c;附送Apple pencil 2&#xff0c;加上Apple Care服务&#xff08;2年&#xff09;&#xff0c;花费&#xffe5;4899&#xff1b; 因为我知道苹果的电池几年下来就不行了…

动态规划(4)---Leetcode.746使用最小花费爬楼梯

题目 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 思路 建…

Python之文件与文件夹操作及 pytest 测试习题

目录 1、文本文件读写基础。编写程序&#xff0c;在 当前目录下创建一个文本文件 test.txt&#xff0c;并向其中写入字符串 hello world。2、编写一个程序 demo.py&#xff0c;要求运行该程序后&#xff0c;生成 demo_new.py 文件&#xff0c;其中内容与demo.py 一样&#xff0…

C语言——求 n 以内(不包括 n)同时能被 3 和 7 整除的所有自然数之和的平方根 s,n 从键盘输入。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> #include<math.h> int main() {int i,n;double s0.0;printf("输入任意一个自然数&#xff1a; ");scanf("%d",&n);for(i1;i<n;i) {if(i%30&&i%70){si;}}ssqrt(s);printf(…

Linux C 目录编程

目录编程 前言目录编程函数mkdir  创建目录rmdir  删除目录opendir  打开目录readdir  读取目录stat  获取文件信息chdir  跳转目录closedir  关闭目录 判断文件类型的宏遍历指定目录及子目录下所有.c文件示例 前言 相较于文件编程&#xff0c;目录编程也有一套自…

druid连接池异常GetConnectionTimeoutException(原创)

问题描述 有天&#xff0c;测试同学突然反馈系统页面查询缓慢&#xff0c;影响使用&#xff0c;我查了日志报&#xff1a; druid 连接池异常 GetConnectionTimeoutException wait millis 9120, active 20, maxActive 20 creating 0 结论先行 经一系列排查&#xff0c;得出数…

在线生成二维码--支持彩色二维码和包含Logo

具体请前往&#xff1a;在线二维码生成工具--可将网址等内容生成为指定大小&#xff0c;指定颜色的彩色二维码,同时支持添加Logo

立冬特辑-----链表OJ题优选合集~~

目录 ​​​​​​​前言&#x1f333; 1.链表中倒数第k个结点&#x1f338; 1.1 思路 1.2 代码 2. 链表的回文结构&#x1fab8; 2.1 思路 2.2 代码 3.相交链表&#x1f32a;️ 3.1 思路 3.2 代码 4.环形链表I&#x1f30a;&#x1f6f3;️ 4.1 思路 4.2 代码 4…

21.合并两个有序链表(LeetCode)

合并两个有序链表&#xff0c;是链表的经典题之一 &#xff0c;这里给出一种经典解法 想法一 创建head和tail两个指针&#xff0c;从头比较两个链表&#xff0c;取小的尾插&#xff0c;注意一开始指针的初始化&#xff0c;接着就是不断利用tail指针&#xff0c;链接比较之中较…

ChatGPT+Roblox,元宇宙的AI叙事逻辑#Leveling Up

MixCopilot 嗨&#xff0c;亲爱的听众朋友们&#xff01;欢迎收听我们的播客节目&#xff01;我是你们的主播&#xff1a;MixCopilot 混合副驾。今天我们要为大家带来的是我们的AI革命系列节目之一。这个系列节目聚焦于AI领域的一些最有影响力的建设者&#xff0c;他们将会讨论…