嵌入式Linux驱动开发 05:阻塞与非阻塞

news2024/11/25 0:43:41

文章目录

  • 目的
  • 基础说明
  • 开发准备
    • 驱动程序
    • 应用程序
  • O_NONBLOCK
    • 应用程序
    • 驱动程序
    • 程序演示
  • poll
    • 应用程序
    • 驱动程序
    • 程序演示
  • 异步通知
    • 应用程序
    • 驱动程序
    • 程序演示
  • 总结

目的

不管在应用开发还是驱动开发中阻塞和非阻塞都是绕不开的话题。这篇文章将介绍相关的基础内容。

这篇文章中内容均在下面的开发板上进行测试:
《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》

这篇文章是在下面文章基础上进行的:
《新唐NUC980使用记录(5.10.y内核):在用户应用中使用GPIO》

基础说明

当应用程序和驱动进行读写交互的时候会有一个问题,你要访问或操作的资源当前是否存在或者是否可用。根据状况和操作逻辑的不同就衍生出了阻塞与非阻塞的概念。

阻塞形式访问的话会直到资源可用才进行下一步操作。非阻塞式操作如果当前资源不可用,要不直接返回错误,要不在后台等到资源可用时候进行通知。

默认情况下应用程序通过 read / write 操作都是阻塞式的,可以通过 open 操作时候传入 O_NONBLOCK 参数设置为非阻塞形式,这样如果资源不可用就会直接返回错误。

除了上面这个操作,还有更多形式的处理这方面问题的机制。比如 select / poll / epoll ,这类方式可以设定一个超时时间,在此时间内会等待资源可用,超时了则会返回。还有比如 异步通知 方式,这个方式有点像是事件或者中断。

开发准备

驱动程序

本文中演示中驱动代码涉及目录与文件结构组织如下:
在这里插入图片描述
具体涉及的文件与内容见前面文章 《嵌入式Linux驱动开发 04:基于设备树的驱动开发》 。

驱动程序编译和拷贝到开发板测试操作如下:

# 进入内核目录
cd ~/nuc980-sdk/NUC980-linux-5.10.y
# 编辑驱动文件
gedit ./drivers/user/char_dev/char_drv.c

# 驱动文件内容见下面章节

# 设置编译工具链
export ARCH=arm; export CROSS_COMPILE=arm-buildroot-linux-gnueabi-
export PATH=$PATH:/home/nx/nuc980-sdk/buildroot-2023.02/output/host/bin

# 编译生成内核镜像
make uImage
# 可以根据电脑配置使用make -jx等加快编译速度

# 编译完成后拷贝到电脑上再拷贝到SD卡中
# sudo cp arch/arm/boot/uImage /media/sf_common/

# 我这里开发环境和开发板在同一局域网中,所以可以直接通过网络将uImage文件拷贝到开发板上
# 在开发板中挂载boot分区
# mount /dev/mmcblk0p1 /mnt/
# 在ubuntu中使用scp命令拷贝dtb文件到开发板上
# scp arch/arm/boot/uImage root@192.168.31.142:/mnt/
# 拷贝完成后重启开发板即可测试
# reboot

应用程序

本文中驱动程序需要编写对应的应用程序来测试其功能,应用程序基础准备如下:

# 创建目应用程序录并进入
mkdir -p ~/nuc980-sdk/apps/test
cd ~/nuc980-sdk/apps/test
# 创建应用程序文件
gedit char_dev_test.c

# 应用程序具体内容因不同的驱动程序而异

# 设置编译工具链
export ARCH=arm; export CROSS_COMPILE=arm-buildroot-linux-gnueabi-
export PATH=$PATH:/home/nx/nuc980-sdk/buildroot-2023.02/output/host/bin

# 编译生成目标应用程序
arm-linux-gcc -o char_dev_test char_dev_test.c

# 编译完成后拷贝到电脑上再拷贝到SD卡中
# sudo cp char_dev_test /media/sf_common/

# 我这里开发环境和开发板在同一局域网中,所以可以直接通过网络将文件拷贝到开发板上
# 在ubuntu中使用scp命令拷贝文件到开发板上
# scp char_dev_test root@192.168.31.142:/root/

O_NONBLOCK

应用程序

这个应用程序中使用 openO_NONBLOCK 来处理是否阻塞。

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

/* 该程序用于读写测试本文要编写的驱动程序对应生成的设备 */
int main(int argc, char **argv)
{
	int fd;
	char buf[4096];
	int len;

	/* 打开文件 */
	if (argc == 2)
	{
		fd = open(argv[1], O_RDWR); // 阻塞方式
	}
	else if ((argc == 3) && (strcmp(argv[2], "-nb") == 0)) 
	{
		fd = open(argv[1], O_RDWR | O_NONBLOCK); // 非阻塞方式
	}
	else 
	{
		printf("Usage: test <devpath>\n"); // 阻塞方式
		printf("       test <devpath> -nb\n");	// 非阻塞方式
		return -1;
	}
	
	if (fd < 0)
	{
		printf("NX applog: can not open file %s\n", argv[1]);
		return -1;
	}

	/* 读数据 */
	len = read(fd, buf, 4096);
	if(len) 
	{
		buf[len] = '\0';
		printf("NX applog: len %d, data %s\n", len, buf);
	}
	else 
	{
		printf("NX applog: len %d, EAGAIN\n", len);
	}
	
	close(fd);

	return 0;
}

驱动程序

下面驱动程序中使用定时器来模拟开关资源可用与否。

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>

static int major = 0;
static const char *char_drv_name = "char_drv";
static struct class *char_drv_class;
static struct device *char_drv_device;

struct timer_list timer;

static bool data_available = false; // 标识当前是否有可用资源
static char *data = "Hello Naisu!";

static void timer_callback(struct timer_list *) // 定时器回调函数
{
	// printk("NX modlog: file %s, func %s, line %d, jiffies %lu.\n", __FILE__, __FUNCTION__, __LINE__, jiffies);
	data_available = (data_available == false) ? true : false; // 定时器中改变资源可用状态
	mod_timer(&timer, jiffies + HZ * 5); // 再次启动定时器
}

static int char_drv_open(struct inode *node, struct file *file)
{
	return 0;
}

static int char_drv_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t char_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int ret;

	if ((file->f_flags & O_NONBLOCK) && (!data_available)) // 如果为非阻塞方式且当前没有资源可用则返回EAGAIN
	{
		printk("NX modlog: O_NONBLOCK and data_available == false.\n");
		return -EAGAIN;
	}

	while(true) {
		if(data_available) {
			ret = copy_to_user(buf, data, strlen(data)); // 从内核空间拷贝数据到用户空间
			break;
		}
		printk("NX modlog: wait for data_available.\n");
		ssleep(1);
	}
	return strlen(data);
}

static const struct file_operations char_drv_fops = {
	.owner = THIS_MODULE,
	.open = char_drv_open,
	.release = char_drv_close,
	.read = char_drv_read,
};

static int __init char_drv_init(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, char_drv_name, &char_drv_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	char_drv_class = class_create(THIS_MODULE, "char_drv_class");
	if (IS_ERR(char_drv_class))
	{
		unregister_chrdev(major, char_drv_name);
		return -1;
	}
	char_drv_device = device_create(char_drv_class, NULL, MKDEV(major, 0), NULL, char_drv_name); // 创建设备节点创建设备节点,成功后就会出现/dev/char_drv_name的设备文件
	if (IS_ERR(char_drv_device))
	{
		device_destroy(char_drv_class, MKDEV(major, 0));
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

	timer_setup(&timer, timer_callback, 0); // 设置定时器和回调函数
	timer.expires = jiffies + HZ * 5;       // 设置定时周期(5秒)
	add_timer(&timer);                      // 启动定时器

	return 0;
}

static void __exit char_drv_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(char_drv_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(char_drv_class);

	unregister_chrdev(major, char_drv_name); // 注销字符设备
}

module_init(char_drv_init); // 模块入口
module_exit(char_drv_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

程序演示

在这里插入图片描述
上面演示中可以看到应用程序在阻塞模式下资源可用会立即返回,资源不可用会阻塞直至获取到资源再返回。而非阻塞模式下不管资源可不可用都会返回。

poll

应用程序

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

/* 该程序用于读写测试本文要编写的驱动程序对应生成的设备 */
int main(int argc, char **argv)
{
	int fd;
	char buf[4096];
	int len;
	int ret;
	int timeout;
	struct pollfd fds;
	nfds_t nfds = 1;

    if (argc != 3)
    {
        printf("Usage: test <devpath> <timeout>\n");
        return -1;
    }

	timeout = atoi(argv[2]);

	/* 打开文件 */
	fd = open(argv[1], O_RDWR); 
	if (fd < 0)
	{
		printf("NX applog: can not open file %s\n", argv[1]);
		return -1;
	}

	/* 等待文件可读 */
	fds.fd = fd;
	fds.events = POLLIN;
    ret = poll(&fds, nfds, timeout); // 等待事件触发, timeout 为 -1 时将不会超时
    if ((ret > 0) && (fds.revents & POLLIN))
    {
        len = read(fds.fd, buf, 4096); // 从文件读取数据
		if(len) 
		{
			buf[len] = '\0';
			printf("NX applog: len %d, data %s\n", len, buf);
		}
    }
    else 
    {
        printf("NX applog: poll timeout or error\n");
    }
	
	close(fd);

	return 0;
}

驱动程序

下面驱动中用到的等待队列(WAIT_QUEUE)。

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/wait.h>

static int major = 0;
static const char *char_drv_name = "char_drv";
static struct class *char_drv_class;
static struct device *char_drv_device;

struct timer_list timer;

static DECLARE_WAIT_QUEUE_HEAD(data_wait); // 定义并初始化等待队列头 data_wait

static bool data_available = false; // 标识当前是否有可用资源
static char *data = "Hello Naisu!";

static int char_drv_open(struct inode *node, struct file *file)
{
	return 0;
}

static int char_drv_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t char_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	wait_event_interruptible(data_wait, data_available); // 资源可用时会立即返回,否则会加入等待队列
	copy_to_user(buf, data, strlen(data));
	return strlen(data);
}

static __poll_t char_drv_poll(struct file *file, poll_table * wait)
{
	// printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(file, &data_wait, wait); // 加入等待队列
	return data_available ? POLLIN : 0;
}

static void timer_callback(struct timer_list *) // 定时器回调函数
{
	// printk("NX modlog: file %s, func %s, line %d, jiffies %lu.\n", __FILE__, __FUNCTION__, __LINE__, jiffies);
	data_available = (data_available == false) ? true : false; // 定时器中改变资源可用状态
	mod_timer(&timer, jiffies + HZ * 5); // 再次启动定时器
	if(data_available)
	{
		wake_up_interruptible(&data_wait); // 如果资源可用则唤醒
	}
}

static const struct file_operations char_drv_fops = {
	.owner = THIS_MODULE,
	.open = char_drv_open,
	.release = char_drv_close,
	.read = char_drv_read,
	.poll = char_drv_poll,
};

static int __init char_drv_init(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, char_drv_name, &char_drv_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	char_drv_class = class_create(THIS_MODULE, "char_drv_class");
	if (IS_ERR(char_drv_class))
	{
		unregister_chrdev(major, char_drv_name);
		return -1;
	}
	char_drv_device = device_create(char_drv_class, NULL, MKDEV(major, 0), NULL, char_drv_name); // 创建设备节点创建设备节点,成功后就会出现/dev/char_drv_name的设备文件
	if (IS_ERR(char_drv_device))
	{
		device_destroy(char_drv_class, MKDEV(major, 0));
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

	timer_setup(&timer, timer_callback, 0); // 设置定时器和回调函数
	timer.expires = jiffies + HZ * 5;       // 设置定时周期(5秒)
	add_timer(&timer);                      // 启动定时器

	return 0;
}

static void __exit char_drv_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(char_drv_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(char_drv_class);

	unregister_chrdev(major, char_drv_name); // 注销字符设备
}

module_init(char_drv_init); // 模块入口
module_exit(char_drv_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

程序演示

在这里插入图片描述
上面驱动刷新资源间隔是5秒,所以应用程序设置5秒的超时时间一定可以获取到资源。

异步通知

应用程序

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

static int fd;

static void sig_callback(int sig) // 收到信号时回调函数
{
	char buf[4096];
	int len;
    len = read(fd, buf, 4096); // 从文件读取数据
	if(len) 
	{
		buf[len] = '\0';
		printf("NX applog: len %d, data %s\n", len, buf);
	}
}

int main(int argc, char **argv)
{
	int	flags;

    if (argc != 2)
    {
        printf("Usage: test <devpath>\n");
        return -1;
    }

	/* 打开文件 */
	fd = open(argv[1], O_RDWR); 
	if (fd < 0)
	{
		printf("NX applog: can not open file %s\n", argv[1]);
		return -1;
	}

    signal(SIGIO, sig_callback); // 注册信号和对应回调函数

	fcntl(fd, F_SETOWN, getpid());
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC); // 启动异步信号通知

	while (1)
	{
		sleep(2);
		printf("NX applog: ......\n");
	}

	close(fd);

	return 0;
}

驱动程序

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

static int major = 0;
static const char *char_drv_name = "char_drv";
static struct class *char_drv_class;
static struct device *char_drv_device;

struct timer_list timer;

static bool data_available = false; // 标识当前是否有可用资源
static char *data = "Hello Naisu!";

struct fasync_struct *data_fasync;

static int char_drv_open(struct inode *node, struct file *file)
{
	return 0;
}

static int char_drv_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t char_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	copy_to_user(buf, data, strlen(data));
	return strlen(data);
}

static int char_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &data_fasync) >= 0) // 保存异步通知设置信息
		return 0;
	else
		return -EIO;
}

static void timer_callback(struct timer_list *) // 定时器回调函数
{
	// printk("NX modlog: file %s, func %s, line %d, jiffies %lu.\n", __FILE__, __FUNCTION__, __LINE__, jiffies);
	data_available = (data_available == false) ? true : false; // 定时器中改变资源可用状态
	mod_timer(&timer, jiffies + HZ * 5); // 再次启动定时器
	if(data_available)
	{
		kill_fasync(&data_fasync, SIGIO, POLL_IN); // 如果资源可用则发送信号
	}
}

static const struct file_operations char_drv_fops = {
	.owner = THIS_MODULE,
	.open = char_drv_open,
	.release = char_drv_close,
	.read = char_drv_read,
	.fasync  = char_drv_fasync,
};

static int __init char_drv_init(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, char_drv_name, &char_drv_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	char_drv_class = class_create(THIS_MODULE, "char_drv_class");
	if (IS_ERR(char_drv_class))
	{
		unregister_chrdev(major, char_drv_name);
		return -1;
	}
	char_drv_device = device_create(char_drv_class, NULL, MKDEV(major, 0), NULL, char_drv_name); // 创建设备节点创建设备节点,成功后就会出现/dev/char_drv_name的设备文件
	if (IS_ERR(char_drv_device))
	{
		device_destroy(char_drv_class, MKDEV(major, 0));
		unregister_chrdev(major, char_drv_name);
		return -1;
	}

	timer_setup(&timer, timer_callback, 0); // 设置定时器和回调函数
	timer.expires = jiffies + HZ * 5;       // 设置定时周期(5秒)
	add_timer(&timer);                      // 启动定时器

	return 0;
}

static void __exit char_drv_exit(void)
{
	printk("NX modlog: file %s, func %s, line %d.\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(char_drv_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(char_drv_class);

	unregister_chrdev(major, char_drv_name); // 注销字符设备
}

module_init(char_drv_init); // 模块入口
module_exit(char_drv_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

程序演示

在这里插入图片描述
上面驱动程序中每两次定时器触发(10s)会发送一次信号,测试的应用程序在接到信号后会执行对应回调函数。

总结

阻塞和非阻塞是非常基础的内容,形式上通常也就着一些,本身使用上来说并不复杂,更多的是需要和实际的业务功能结合起来处理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis命令-认识NoSQl和Redis常见的通用命令

1. 认识NoSQL 非关系型数据库 NoSQL是指一类非关系型数据库&#xff0c;它们采用的数据模型不同于传统的关系模型&#xff0c;它通常使用键值对、文档、图形等非传统的数据结构进行数据存储&#xff0c;不遵循预定义的模式和模型。NoSQL数据库通常分布式、高可扩展性&#xff0…

理解Java ThreadLocal

原文链接 理解Java ThreadLocal ThreadLocal是Java提供的为每个线程存储线程独立的数据的存储方式&#xff0c;也就是说用ThreadLocal来保存的数据&#xff0c;只能被当前线程所访问&#xff0c;其他线程无法访问&#xff0c;因为只有&#xff08;一个线程&#xff09;当前线程…

六级备考6天|CET-6|听力第五六讲|6月11日|19:44~20:07

目录 第五讲 第六讲 第五讲 第六讲 ​​​​​​​

支付宝认证的作用是什么?考试方向有哪些?

通过考证书来提升自己的能力&#xff0c;是现在的打工人经常会做的事&#xff0c;而对于从事信息通信技术行业的人来说&#xff0c;可以选择的证书非常多&#xff0c;其中支付宝认证是一个新设立的证书&#xff0c;但是实用性非常高。支付宝作为我们生活中常用的软件&#xff0…

arthas 的初使用

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

PD QC诱骗取电应用IC《乐得瑞LDR6328S》广泛应用于各大小家电

随着现在智能家居的应用越来越广泛&#xff0c;带电池的产品一天比一天多&#xff0c;今天这篇文章就来讲一下那些支持快速充电(PD QC)的产品应用电路是怎么实现的 USB PD受电端取电芯片&#xff08;乐得瑞取电受电)LDR6328S 支持多协议快充取电&#xff01; 1、概述 LDR6328S…

推荐5款你可能没见过的效率软件

你有没有想过&#xff0c;有些软件能让你的电脑用起来更方便&#xff0c;更快&#xff0c;更好看&#xff1f;这篇文章就为你介绍了五款这样的软件&#xff0c;它们分别是BreeZip&#xff0c;ClipClip&#xff0c;燃精灵&#xff0c;Sticky Notes和Tabby。下面我们来看看它们都…

增强 Kubernetes 可观测性:API Server Tracing 特性已到达 Beta 版本

标题 在分布式系统中&#xff0c;很难弄清楚问题在哪里。 想象一个场景&#xff0c;这也是 Kubernetes 集群管理员最常遇到的问题&#xff0c;Pod 无法正常启动&#xff0c;这时候作为管理员&#xff0c;我们会先去思考这可能是哪个组件出了问题&#xff0c;然后去对应的组件查…

Parallel Desktop下的Centos 9 ping通网络,配置静态ip的全过程

目录 一、发现问题1. 找不到网卡配置文件2. 网络重启的命令一直无法执行成功 二、分析问题三、解决问题系统环境1. 打开网卡配置文件2. 修改ipv4配置3. 重载网卡配置文件4. ping通&#xff0c;可以正常上网了 四、疑问1. 如何确定自己是不是设置了静态ip2. DHCP是固定静态ip 的…

OC(iOS)中常见的面试题汇整(大全)

你如何理解OC这门语言的?谈一下你对OC的理解? OC语言是C语言的一个超集,只是在C语言的基础上加上了面向对象的语言特征,如:继承,封装,多态. 封装:把属性和方法封装成一个类,方便我们使用 多态:不同对象对于同一消息的不同响应,子类可以重写父类的方法,且…

SpringMVC简介及入门案例

1.SpringMVC简介 SpringMVC是一种基于Java实现MVC模型的轻量级Web框架优点&#xff1a;相较于Servlet使用简单&#xff0c;开发便捷。灵活性比较强。 后端做表现层技术开发的框架有Servlet&#xff0c;SpringMVC技术同样也是做表现层技术开发框架&#xff0c;JDBC以及Mybatis数…

Linux内核中内存管理相关配置项的详细解析6

接前一篇文章&#xff1a;Linux内核中内存管理相关配置项的详细解析5 六、Memory hotplug 此项只有选中和不选中两种状态&#xff0c;默认为选中。 此项展开后如下图所示&#xff1a; 1. Online the newly added memory blocks by default 对应配置变量为&#xff1a;CONFIG…

神经网络编程基础

1、二分类(Binary Classification) &#xff08;1&#xff09;、逻辑回归(logistic regression)是一个用于二分类(binary classification)的算法。所谓二分类是由输入到判断输出结果是或者不是。比如输入一个包含动物的图片&#xff0c;判断这张图片中的动物是否包含猫&#x…

成为优秀自动化测试工程师的7个步骤

在这里&#xff0c;我将详细解释成为测试自动化工程师的七个最重要的步骤。因此&#xff0c;所有希望将职业转向自动化测试的人都要注意所有这些。 1. 不要忽视手动测试 虽然我了解公司正在转向无代码自动化测试工具&#xff0c;达到专家级别并跟上行业自动化测试工程师的竞争…

ELK 日志采集使用

1.安装ELK整体环境 1.1.安装docker环境 Docker 最新版Version 20.10安装_docker最新版本是多少_猿小飞的博客-CSDN博客 1.2.先安装docker compose 安装docker compose_猿小飞的博客-CSDN博客 1.3.使用 Docker Compose 搭建 ELK 环境 1.3.1.编写 docker-compose.yml 脚本启…

从增强器Advisor窥探AOP原理

Spring创建Aop代理过程 AbstractAutowireCapableBeanFactory Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)Object applyBeanPostProcessorsBeforeInsta…

【算法基础】常数操作 时间复杂度 选择排序 冒泡排序 插入排序 位运算

常数操作 定义 一个操作如果和样本的数据量没有关系&#xff0c;每次都是固定时间内完成的操作叫做常数操作&#xff0c;比如常见的计算操作&#xff1a;加减乘除。 取出数组中任意位置元素可以叫做常数操作&#xff0c;因为数组的地址是连续的&#xff0c;计算机取的时候可以…

本地加密传输测试-业务安全测试实操(2)

3个测试点:加密传输,session会话,session注销会话 测试原理和方法 本机加密传输测试是针对客户端与服务器的数据传输,查看数据是否采用SSL (Security Socket Layer ,安全套接层)加密方式加密。 测试过程 测试验证客户端与服务器交互数据在网络传输过程中是否采用 SSL 进…

Linux基础知识4

Linux基础知识 适合有Linux基础的人群进行复习。 禁止转载&#xff01; shell编程 shell第一行内容格式&#xff1f; #!bin/sh&#xff0c;#!bin/bash&#xff0c;#!/bin/csh&#xff0c;#!/bin/tcsh或#!/bin/ksh等 执行shell脚本的三种方式 (1)为shell脚本直接加上可执行权…