Linux驱动开发——字符设备(2)

news2024/11/16 9:57:53

目录

虚拟串口设备驱动

一个驱动支持多个设备

习题


虚拟串口设备驱动


        字符设备驱动除了前面搭建好代码的框架外,接下来最重要的就是要实现特定于设备的操作方法,这是驱动的核心和关键所在,是一个驱动区别于其他驱动的本质所在,是整个驱动代码中最灵活的代码所在。了解了虚拟串口设备的工作方式后,接下来就可以针对性的编写驱动程序,代码如下:

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

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"

static struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);

static int vser_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
	unsigned int copied = 0;
	kfifo_to_user(&vsfifo, buf, count, &copied);
	return copied;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
	unsigned int copied = 0;
	kfifo_from_user(&vsfifo, buf, count, &copied);
	return copied;
}

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
};

static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if(ret)
			goto reg_err;
	cdev_init(&vsdev, &vser_ops);
	vsdev.owner = THIS_MODULE;
	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
	if (ret)
		goto add_err;
	return 0;

add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);

reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	dev_t dev;
	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	cdev_del(&vsdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        新的驱动在代码第15行定义并初始化了一个名叫 vsfifo 的 struct kfifo 对象,每个元素的数据类型为char,共有32个元素的空间。代码第17行到第25行设备打开和关闭函数,分别对应于file_operations 内的open和release 方法。因为是虚拟设备,所以这里并没有需要特别处理的操作,仅仅返回 0表示成功。这两个函数都有两个相同的形参,第一个形参是要打开或关闭文件的inode,第二个形参则是打开对应件后由内核构造并初始化好的file结构,在前面我们已经较深入地分析了这两个对象的作用。这里之所以叫release而不叫close是因为一个文件可以被打开多次,那么vser_open函数相应地会被调用多次,但是关闭文件只有到最后一个close操作才会导致vser_release函数被调用,所以用 release 更贴切。
        代码第27第34行是read系统调用驱动实现,这里主要是把FIFO中的数据返回给用户层,使用了kfifo_to_user 这个宏。read系统调用要求用户返回实际读取的字节数,而copied变量的值正好符合这一要求。代码36到第43对应的write系统调用的驱动实现,同read系统调用一样,只是数据流向相反而已。
        读和写函数引入了3个新的形参,分别是buf,count和pos,根据上面的代码,已经不难发现它们的含义。buf代表的是用户空间的内存起始地址;count表示用户想要读写多少个字节的数据:而pos是文件的位置指针,在虚拟串口这个不支持随机访问的设备中,该参数无用。_user是提醒驱动代码编写者,这个内存空间属于用户空间。
        代码第 47 行到第 50 行是将file_operations中的函数指针分别指向上面定义的函数这样在应用层发生相应的系统调用后,在驱动里面的函数就会被相应地调用。上面这个示例实现了一个功能非常简单,但是基本可用的虚拟串口驱动程序。按照下面的步骤可以进行验证。


        通过实验结果可以看到,对/dev/vser0写入什么数据,就可以从这个设备读到什么数据,和一个具备内环回功能的串口是一致的。
        为了方便读者对照查阅,特将file_operations结构类型的定义代码列出。从中我们可以看到,还有很多接口函数还没有实现,在后面的章节中,我们会陆续再实现一些接口。显然,一个驱动对下面的接口的实现越多,它对用户提供的功能就越多,但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问,那么llseek函数接口自然就不用实现。

1525 struct file_operations {
1526 struct module *owner; 
1527 loff_t (*llseek) (struct file *, loff t, int); 
1528 ssize_t (*read) (struct file *, char__user *, size t, loff t *); 
1529 ssize_t (*write) (struct file *, const char _user *, size t, loff t*); 
1530 ssize t (*alo read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1531 ssize t (*aio write) (struct kiocb *, const struct iovec *, unsigned long, loff t);

1532 int (iterite) latruct tile ', atruet dir_context *);
1533 unsigned int (*poll) (struct file *, strunt poll_table_struct *);
1534 long (unlocked ioctl) (struct file *, unsigned int, unsigned  long);

1535 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

1536 int (*mmap) (struct file *, struct vm_area_struct *);
1537 int (*open) (struct inode *, struct file *); 
1538 int (*flush) (struct file *, f1_owner_t id); 
1539 int (*release) (struct inode *, struct file *); 
1540 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1541 int (*aio_fsync) (struct kiocb *, int datasync); 
1542 int (*fasync) (int, struct file *, int); 
1543 int (*lock) (struct file *, int, struct file_lock *); 
1544 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,int);
1545 unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1546 int (*check_flags)(int); 
1547 int (*flock) (struct file *, int, struct file_lock *); 
1548 ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, lofft *, size_t, unsigned int);
1549 ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1550 int (*setlease) (struct file *, long, struct file_lock **); 
1551 long (*fallocate)(struct file *file, int mode, loff_t offset, 
1552 loff_t len); 
1553 int (*show_fdinfo)(struct seq_file *m, struct file *f); 
1554 };


一个驱动支持多个设备


        如果一类设备有多个个体(比如系统上有两个串口),那么我们就应该写一个驱动来支持这几个设备,而不是每一个设备都写一个驱动。对于多个设备所引入的变化是什么呢?首先我们应向内核注册多个设备号,其次就是在添加 cdev对象时指明该cdev对象管理了多个设备;或者添加多个 cdev 对象,每个cdev对象管理一个设备。接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO 进行操作)?观察读和写函数,没有发现能够区别设备的形参。再观察open 接口,我们会发现有一个inode形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。因此,我们可以在open接口函数中取出这些信息,并存放在file结构对象的某个成员中,再在读写的接口函数中获取该 file 结构的成员,从而可以区分出对哪个设备进行操作。
        下面首先展示用一个 cdev实现对多个设备的支持

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

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"

static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);


static int vser_open(struct inode *inode, struct file *filp)
{
	switch (MINOR(inode->i_rdev)) {
		default:
		case 0:
			filp->private_data = &vsfifo0;
			break;
		case 1:
			filp->private_data = &vsfifo1;
			break;
	}
	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
	unsigned int copied = 0;
	struct kfifo *vsfifo = filp->private_data;

	kfifo_to_user(vsfifo, buf, count, &copied);

	return copied;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
	unsigned int copied = 0;
	struct kfifo *vsfifo = filp->private_data;

	kfifo_from_user(vsfifo, buf, count, &copied);

	return copied;
}

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
};

static int __init vser_init(void)
{
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if(ret)
			goto reg_err;
	cdev_init(&vsdev, &vser_ops);
	vsdev.owner = THIS_MODULE;
	ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
	if (ret)
		goto add_err;
	return 0;

add_err:
	unregister_chrdev_region(dev, VSER_DEV_CNT);

reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	dev_t dev;
	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	cdev_del(&vsdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        上面的代码针对前一示例做的修改是:将VSER_DEV_CNT定义为2,表示支持两个设备;用DEFINE_KFIFO,分别是vsfifo0和vsfifo1(很显然,这里动态分配FIFO要优于静态定义,但是这会涉及后面章节中内核内存分配的相关知识,故此使用静态的方法);在open接口函数中根据次设备号的值来确定保存哪个FIFO结构体的地址到file结构中的private_data成员中,file结构中的private_data是一个void *类型的指针,内核保证不会使用该指针,所以正如其名一样,是驱动私有的;在读写接口函数中则是先从file结构中取出private_data的值,即FIFO结构的地址,然后再进一步操作。

        接下来演示如何将每一个edev对象对应到一个设备来实现一个驱动对多个设备的支持

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

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"

static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);

struct vser_dev {
	struct kfifo *fifo;
	struct cdev cdev;
};

static struct vser_dev vsdev[2];

static int vser_open(struct inode *inode, struct file *filp)
{	
	filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
	unsigned int copied = 0;
	struct vser_dev *dev = filp->private_data;

	kfifo_to_user(dev->fifo, buf, count, &copied);

	return copied;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
	unsigned int copied = 0;
	struct vser_dev *dev = filp->private_data;

	kfifo_from_user(dev->fifo, buf, count, &copied);

	return copied;
}

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
};

static int __init vser_init(void)
{
	int i;
	int ret;
	dev_t dev;

	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
	if(ret)
			goto reg_err;
	for( i = 0; i < VSER_DEV_CNT; i++) {
		cdev_init(&vsdev[i].cdev, &vser_ops);
		vsdev[i].cdev.owner = THIS_MODULE;
		vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1; 
		ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
		if (ret)
			goto add_err;
	}
	return 0;

add_err:
	for(--i;i>0;--i)
		cdev_del(&vsdev[i].cdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);

reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	int i;
	dev_t dev;
	dev = MKDEV(VSER_MAJOR,VSER_MINOR);
	for(i = 0; i < VSER_DEV_CNT; i++)
		cdev_del(&vsdev[i].cdev);
	unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

        代码第17行至第20行新定义了一个结构类型vser_dev,代表一种具体的设备类,通常和设备相关的内容都应该和cdev定义在一个结构中。如果用面向对的思想理解这种做法将会变得很容易。cdev是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的强性,比如vser_dev中的fifo,这样子类就更能刻画好一类具体的设备。代码第22行创建了两个vser_dev类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存并没有调用构造函数来初始化这两个对象,但在代码的第 74行到第77行完成了这个作。查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。代码的第 74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo成员的指向。这里需要说明的是,用 DEFINE_KFIFO 定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说 vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。
        代码第26行用到了一个container_of宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。它的作用就是根据结构成员的地址来反向得到结构的起始地址。在代码中,inode->i_cdev给出了struct vser_dev结构类型中cdev成员的地址(见图3.2),通过container_of宏就得到了包含该 cdev的结构地址。
使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。

make ARCH=arm

./lazy

上面再ubuntu中下面再开发板中

mknod /dev/vser0 c 256 0
mknod /dev/vser1 c 256 1

depmod

modprobe vser

echo "11111" > /dev/vser0

echo "22222" > /dev/vser1

cat /dev/vser0

cat /dev/vser1

这俩就会分别打印出来。没带开发板,但是现象绝对没问题。

习题

1.字符设备和块设备的区别不包括( B)。
[A]字符设备按字节流进行访问,块设备按块大小进行访问

[B]字符设备只能处理可打印字符,块设备可以处理二进制数据

[C]多数字符设备不能随机访问,而块设备一定能随机访问

[D] 字符设备通常没有页高速缓存,而块设备有

2.在3.14.25 版本的内核中,主设备号占(C )位,次设备号占(D )位。
[A]8 [B]16 [C] 12 [D] 20 
3.用于分配主次设备号的函数是(C )。
[A]register_chrdev_region [B] MKDEV 
[C]alloc_chrdev_region [D] MAJOR 
4.在字符设备驱动中,struct file_operations 结构中的函数指针成员不包含( B)。
[A]open [B]close [C] read [D] show_fdinfo 

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

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

相关文章

vue:生成二维码 qrcode、vue-qr(二维码中间可带logo)

一、方法一 qrcode qrcode - npm 1.1、安装 yarn add qrcode 1.2、页面引入 import QRCode from qrcode; 1.3、方法里边使用 getQRCodeUrl(){ QRCode.toDataURL(hello world,{color: {dark:"#010599FF",light:"#FFBF60FF"}}).then((url) > {// 获…

【华为OD机试真题】查找树中元素(查找二叉树节点)(javaC++python)100%通过率

查找树中元素 知识点树BFSQ搜索广搜 时间限制:1s空间限制:256MB限定语言:不限 题目描述: 已知树形结构的所有节点信息,现要求根据输入坐标(x,y)找到该节点保存的内容 值;其中: x表示节点所在的层数,根节点位于第0层,根节点的子节点位于第1层,依次类推; y表示节…

智慧井盖-物联网智能井盖系统-管网数字化监测,守护城市生命线

平升电子智慧井盖-物联网智能井盖系统-管网数字化监测,守护城市生命线实现对井下设备和井盖状态的监测及预警&#xff0c;是各类智慧管网管理系统中不可或缺的重要设备&#xff0c;解决了井下监测环境潮湿易水淹、电力供应困难、通讯不畅等难题&#xff0c; 适合安装于城市主干…

U盘独个文件不能超过4GB的原因——U盘的文件系统

U盘独个文件不能超过4GB的原因——文件系统一 背景1.1 文件系统1.2 “簇”/”群集“1.3 文件系统的历史1.3.1 FAT1.3.2 exFAT&#xff08;扩展文件分配表&#xff09;1.3.3 NTFS1.3.4 HFS1.3.5 APFS1.4 文件系统缺陷的渊源1.5 文件系统缺陷的解释1.6 报错原因二 文件系统的比较…

详细解读appium怎样连接多台设备

我们在做app自动化的时候&#xff0c;若要考虑兼容性问题&#xff0c;需要跑几台设备&#xff0c;要是一台一台的跑比较耗 时&#xff0c;因此需要考虑使用多线程来同时操作多台设备。 1.我们拿两台设备来模拟操作下&#xff0c;使用&#xff1a;adb devices查看连接状况&…

考研数二第十四讲 牛顿-莱布尼茨公式与用定义法求解定积分

牛顿-莱布尼茨公式 牛顿-莱布尼茨公式在微分与积分以及不定积分与定积分之间架起了一座桥梁&#xff0c;因此&#xff0c;这个公式又被称为微积分基本公式。 微积分基本公式的简单推导 在看微积分基本公式之前&#xff0c;我们先来看一个有点特殊的函数&#xff0c;积分上限…

全网最详细SUMO仿真软件教程——入门篇

目录SUMO下载前提知识使用netedit创建路网需求生成SUMO-GUI可视化SUMO下载 SUMO官网: SUMO下载链接 配置SUMO_HOME系统变量&#xff0c;后续引入包需要。 前提知识 sumo仿真器跑起来需要三个文件&#xff0c;分别是Network、Route以及SUMO configuration file。 在sumo中&a…

公司刚来的00后真卷,上班还没2年,跳到我们公司起薪20k....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。 这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作都没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了…

fl studio插件在哪个文件夹里 fl studio插件怎么用

fl studio是一个全能数字音乐工作台&#xff0c;集编曲、剪辑、录音和混音为一体&#xff0c;致力于把电脑变为全功能音乐工作室。fl studio具有专业的调音台&#xff0c;提供有复杂作品所需的所有功能&#xff0c;另外fl studio的Pattern和Song模式可以更加快速的制作Hip-hop、…

mysql执行计划解读

1.如何查看mysql执行计划 explain select * from t1; desc select * from t1; explain partitions select * from t1; 用于分区表的explain 2.执行计划包含的信息 "rootlocalhost:mysql.sock [db1]>explain select * from t1\G; *************************** 1. ro…

【JAVA真的没出路了吗?】

2023年了&#xff0c;转行IT学习Java是不是已经听过看过很多次了。随之而来的类似学Java没出路、Java不行了、对Java感到绝望等等一系列的制造焦虑的话题也在网上层出不穷&#xff0c;席卷了一大片的对行业不了解的吃瓜群众或是正在学习中的人。如果是行外人真的会被这种言论轻…

Redis_BigKey

面试题 阿里广告平台&#xff0c;海量数据里查询某一固定前缀的key 小红书&#xff0c;你如何生厂上限值key */flushdb/flushall等危险命令以防止误删误用&#xff1f; 美团&#xff0c;MEMORY USAGE 命令你用过吗&#xff1f; BigKey问题&#xff0c;多大算big&#xff1f;你如…

Spring5源码深度解析---Spring整体架构

概述 Spring是2003 年兴起的一个轻量级的Java 开发框架&#xff0c;从Rod Johnson著作中的部分理念和原型衍生而来。Spring是一个开放源代码的设计层面框架&#xff0c;为了解决企业应用开发的复杂性而创建。将面向接口的编程思想贯穿整个系统应用&#xff0c;使用基本的JavaB…

5分钟学会Ribbon负载均衡

文章目录一、Ribbon1.1 Ribbon的负载均衡流程&#xff1a;1.2 负载均衡策略1.2.1 内置的负载均衡策略1.2.2 如何修改负载均衡1.3 加载方式一、Ribbon 1.1 Ribbon的负载均衡流程&#xff1a; 获取可用的服务列表&#xff1a;客户端在进行服务调用之前&#xff0c;首先需要获取可…

浅谈人工智能在教育行业的应用

人工智能&#xff08;Artificial Intelligence, AI&#xff09;是当前最热门的技术领域之一&#xff0c;也是未来的发展趋势之一。人工智能可以用于各种领域&#xff0c;包括医疗、金融、交通、农业等。其中&#xff0c;人工智能在教育行业的应用也备受关注。本文将从人工智能在…

【无功优化】基于改进遗传算法的电力系统无功优化研究【IEEE30节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【k8s完整实战教程1】源码管理-Coding

系列文章&#xff1a;这个系列已完结&#xff0c;如对您有帮助&#xff0c;求点赞收藏评论。 读者寄语&#xff1a;再小的帆&#xff0c;也能远航&#xff01; 【k8s完整实战教程0】前言【k8s完整实战教程1】源码管理-Coding【k8s完整实战教程2】腾讯云搭建k8s托管集群【k8s完…

计算机系统概论

提示&#xff1a;星河不问赶路人&#xff0c;岁月不负有心人 文章目录前言知识1.1 计算机的发展1.2 计算机硬件的基本组成1.3 计算机的性能指标前言知识 机器字长&#xff1a;计算机一次整数运算所能处理的二进制位数 .exe文件就是用机器语言描述的程序 1.1 计算机的发展 计…

LSPosed 安装教程(LSP框架安装教程)

1、下载LSPosed模块 CSDN下载&#xff1a; Riru 版&#xff1a;LSPosed-RiruZygisk版&#xff1a; LSPosed-Zygisk 或 github下载&#xff1a;LSPosed GitHub 2、打开Magisk – 设置 – 开启 Zygisk 3、打开面具 – 模块 – 从本地安装 4、重启设备&#xff0c;通知栏 点开&…

elasticsearch MySQL 数据同步。

elasticsearch & MySQL 数据同步。 文章目录elasticsearch & MySQL 数据同步。3. 数据同步。3.1. 思路分析。3.1.1. 同步调用。3.1.2. 异步通知。3.1.3. 监听 binlog。3.1.4. 选择。3.2. 实现数据同步。3.2.1. 思路。3.2.2. 导入 demo。3.2.3. 声明交换机、队列。1&…