day4-字符设备驱动基础上_基础框架

news2024/9/21 0:51:45

大纲:

设备分类

申请和注销设备号

函数指针复习

注册字符设备

字符设备驱动框架解析

读操作实现

写操作实现

ioctl操作实现

printk

多个次设备的支持

一、Linux内核对设备的分类

linux的文件种类:

  1. -:普通文件 (文件内容、文件名、文件元信息)

  2. d:目录文件

  3. p:管道文件 (没有文件内容)

  4. s:本地socket文件 (没有文件内容)

  5. l:链接文件

  6. c:字符设备 (没有文件内容)

  7. b:块设备 (没有文件内容)

Linux内核按驱动程序实现模型框架的不同,将设备分为三类:

  1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存

  2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率

  3. 网络设备:针对网络数据收发的设备

 总体框架图

 二、设备号

内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:

  1. 主设备号:占高12位,用来表示驱动程序相同的一类设备

  2. 次设备号:占低20位,用来表示被操作的哪个具体设备

应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。

MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:

dev_t devno;
int major = 251;//主设备号
int minor = 2;//次设备号
devno = MKDEV(major,minor);

MAJOR宏用来从32位设备号中分离出主设备号,用法:

dev_t devno = MKDEV(249,1);
int major = MAJOR(devno);

MINOR宏用来从32位设备号中分离出次设备号,用法:

dev_t devno = MKDEV(249,1);
int minor = MINOR(devno);

如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:

@ cd /dev
@ mknod 设备文件名 设备种类(c为字符设备,b为块设备)  主设备号  次设备号    //ubuntu下需加sudo执行

在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:

int mknod(const char *pathname,mode_t mode,dev_t dev);
pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下
mode:文件权限 位或 S_IFCHR/S_IFBLK
dev:32位设备号
返回值:成功为0,失败-1

三、申请和注销设备号

字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:

  1. 申请设备号

  2. 定义、初始化、向内核添加代表本设备的结构体元素

int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
	from:自己指定的设备号
	count:申请的设备数量
	name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
	成功为0,失败负数,绝对值为错误码
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
	dev:分配设备号成功后用来存放分配到的设备号
	baseminior:起始的次设备号,一般为0
	count:申请的设备数量
	name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
	成功为0,失败负数,绝对值为错误码
	
	//注意成功后要使主设备号赋值为dev

 分配成功后使用cat指令在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息

void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
	from:已成功分配的设备号将被释放
	count:申请成功的设备数量

 释放后/proc/devices文件对应的记录消失

四、函数指针复习 

内存的作用---用来存放程序运行过程中的

1.数据

2.指令

4.1、内存四区

堆区 (数据)

栈区 (数据)

数据区 (数据)

代码区 (代码)

4.2、C语言中内存数据的访问方式

直接访问:通过所在空间名称去访问

间接访问:通过所在空间首地址去访问 *地址值 此时的*为间接访问运算符

4.3、C语言中函数调用方式:

直接调用:通过函数名去调用函数

间接调用:通过函数在代码区所对应的那份空间的首地址去调用

int func(int a,int b)
{
    //......
}

int (int a,int b)  * pf;//语法错误
int *pf(int a,int b);//函数声明语句
int (*pf)(int a,int b);//定义一个函数指针
pf = &func;//&运算符后面如果是函数名的话可以省略不写
pf = func;

y = func(3,4);//直接调用
y = (*pf)(3,4);//间接调用,*运算符后面如果是函数指针类型则可以省略不写
y = pf(3,4);//间接调用

typedef int myint;
typedef int (*)(int,int)  pft;//语法错误
typedef int (*pft)(int,int) ;
pft pt;

 五、注册字符设备

struct cdev
{
	struct kobject kobj;//表示该类型实体是一种内核对象
	struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
	const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
	struct list_head list;//链表指针域
	dev_t dev;//设备号
	unsigned int count;//设备数量
};

struct cdev 是 struct kobject 的子类

内核采用了hash链表的数据结构形式来管理批量的设备

自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:

  1. 直接定义:定义结构体全局变量

  2. 动态申请:

    struct cdev * cdev_alloc()

给cdev对象指定操作函数集 

void cdev_init(struct cdev *cdev,const struct file_operations *fops)

struct file_operations 
{
   struct module *owner;           //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
   int (*open) (struct inode *, struct file *);	//打开设备
   int (*release) (struct inode *, struct file *);	//关闭设备
   ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);	//读设备
   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);    //写设备
   loff_t (*llseek) (struct file *, loff_t, int);		//定位
   long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
   unsigned int (*poll) (struct file *, struct poll_table_struct *);	//POLL机制,实现多路复用的支持
   int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
   int (*fasync) (int, struct file *, int); //信号驱动
   //......
};

一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化

该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:  

int cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
	p:指向被添加的设备
	dev:设备号
	count:设备数量,一般填1
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
	p:指向被移除的字符设备

字符设备驱动开发步骤:

  1. 如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev

  2. 定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)

  3. 定义三个全局变量分别来表示主设备号、次设备号、设备数

  4. 定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE

  5. module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE e. 添加字符设备到内核

  6. module exit函数:a. 注销设备号 b. 从内核中移除struct cdev c. 如果如果是全局设备指针则释放其指向空间

  7. 编写各个操作函数并将函数名初始化给struct file_operations结构体变量

验证操作步骤:

  1. 编写驱动代码mychar.c

  2. make生成ko文件

  3. insmod内核模块

  4. 查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字

  5. 创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号

  6. 编写app验证驱动(testmychar_app.c)

  7. 编译运行app,dmesg命令查看内核打印信息

 

 六、字符设备驱动框架解析

设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:

驱动实现设备操作函数 ----------- 做桩

insmod调用的init函数主要作用 --------- 钉桩

rmmod调用的exitt函数主要作用 --------- 拔桩

应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩

6.1 两个操作函数中常用的结构体说明

内核中记录文件元信息的结构体
struct inode
{
	//....
	dev_t  i_rdev;//设备号
	struct cdev  *i_cdev;//如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
	//.... 
}
/*
	1. 内核中每个该结构体对象对应着一个实际文件,一对一
	2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
	3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/
读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)
struct file
{
	//...
	mode_t f_mode;//不同用户的操作权限,驱动一般不用
	loff_t f_pos;//position 数据位置指示器,需要控制数据开始读写位置的设备有用
	unsigned int f_flags;//open时的第二个参数flags存放在此,驱动中常用
	struct file_operations *f_op;//open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
	void *private_data;//本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
	struct dentry *f_dentry;//驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
    int refcnt;//引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
	//...
};
/*
	1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
	2. open同一个文件多次,每次open都会创建一个该类型的对象
	3. 文件描述符数组中存放的地址指向该类型的对象
	4. 每个文件描述符都对应一个struct file对象的地址
*/

6.2 字符设备驱动程序框架分析

驱动实现端 

 驱动使用端

读写操作以及ioctl实现实验代码

mychar.h

#ifndef MY_CHAR_H
#define MY_CHAR_H

#include <asm/ioctl.h>

#define MY_CHAR_MAGIC 'k'

#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)

#endif

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "mychar.h"

#define BUFLEN 100
int major = 11;//主设备号
int minor = 0;//次设备号
int mychar_num = 1;//设备数量

struct mychar_dev{
	struct cdev mydev;

	char mydev_buf[BUFLEN];
	int curlen;
};

struct mychar_dev gmydev;



int mychar_open(struct inode *pnode,struct file *pf){
	pf->private_data = (void *)(container_of(pnode->i_cdev,struct mychar_dev,mydev));
	printk("myopen is called\n");
	return 0;
}

int mychar_close(struct inode * pnode,struct file *pf){
	printk("myclose is called\n");
	return 0;
}

ssize_t mychar_read(struct file *pf,char __user *puser,size_t count,loff_t *p_pos){
	struct mychar_dev *pmydev = (struct mychar_dev *)(pf->private_data);
	int size = 0;
	int ret = 0;
	if(count>pmydev->curlen){
		size = pmydev->curlen;
	}else{
		size = count;
	}
	printk("mychar_read is called\n");
	ret = copy_to_user(puser,pmydev->mydev_buf,size);
	printk("ret = %d\n",ret);
	if(ret){
		printk("copy_to_user is failed\n");
		return -1;
	}
	memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);

	pmydev->curlen = pmydev->curlen - size;

	return size;

}

ssize_t mychar_write(struct file *pf,const char __user *puser,size_t count,loff_t *p_pos){
	struct mychar_dev *pmydev = (struct mychar_dev *)(pf->private_data);
	int size = 0;
	int ret = 0;
	if(count>BUFLEN-pmydev->curlen){
		size = BUFLEN-pmydev->curlen;
	}else{
		size = count;
	}
	printk("mychar_write is called\n");
	ret = copy_from_user(pmydev->mydev_buf+pmydev->curlen,puser,size);
	printk("ret = %d\n",ret);
	if(ret){
		printk("copy_from_user is failed\n");
	}
	
	pmydev->curlen = pmydev->curlen + size;
	
	return size;
}

long mychar_ioctl(struct file *pf,unsigned int cmd,unsigned long arg){
	int __user *pret = (int *)arg;
	int maxlen = BUFLEN;
	int ret = 0;
	
	struct mychar_dev *pmydev = (struct mychar_dev *)(pf->private_data);
	
	switch(cmd){
	case MYCHAR_IOCTL_GET_MAXLEN:
		ret = copy_to_user(pret,&maxlen,sizeof(int));
		break;
	case MYCHAR_IOCTL_GET_CURLEN:
		ret =copy_to_user(pret,&pmydev->curlen,sizeof(int)); 
		break;
	default:
		return -1;
	}
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);
	/*申请设备号*/
	ret = register_chrdev_region(devno,mychar_num,"mychar");
	if(ret){
		ret = alloc_chrdev_region(&devno,minor,mychar_num,"mychar");
		if(ret<0){
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//注意这句容易忘记
	}
	/*给struct cdev对象指定操作函数集*/
	cdev_init(&gmydev.mydev,&myops);

	/*将STRUCT对象添加到内核对应的数据结构里*/
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev,devno,mychar_num);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno,mychar_num);
	printk("mychar will exit\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

testmychar_app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "mychar.h"
#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
	char mybuf[8] ="";
	int fd = -1;
	int max = 0;
	int cur = 0;
	if(argc<2){
		printf("The argument is too few\n");
		return 1;
	}
	fd = open(argv[1],O_RDWR);
	if(fd<0){
		printf("open %s failed\n",argv[1]);
		return 2;
	}
	
	ioctl(fd,MYCHAR_IOCTL_GET_MAXLEN,&max);
	printf("maxlen = %d\n",max);
	
	write(fd,"hello",6);
	ioctl(fd,MYCHAR_IOCTL_GET_CURLEN,&cur);
	printf("curlen = %d\n",cur);
	read(fd,mybuf,8);
	printf("buf = %s\n",mybuf);

	close(fd);
	fd=-1;
	return 0;
}

printk

//日志级别
#define	KERN_EMERG	"<0>"	/* system is unusable			*/
#define	KERN_ALERT	"<1>"	/* action must be taken immediately	*/
#define	KERN_CRIT	"<2>"	/* critical conditions			*/
#define	KERN_ERR	"<3>"	/* error conditions			*/

#define	KERN_WARNING	"<4>"	/* warning conditions			*/

#define	KERN_NOTICE	"<5>"	/* normal but significant condition	*/
#define	KERN_INFO	"<6>"	/* informational			*/
#define	KERN_DEBUG	"<7>"	/* debug-level messages			*/

用法:printk(KERN_INFO"....",....)
    
    printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")

多个次设备支持

利用数组来创建、for循环来创建已经分配设备号、注销设备号

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

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

相关文章

HashMap 的 7 种遍历方式与性能分析

随着 JDK 1.8 Streams API 的发布&#xff0c;使得 HashMap 拥有了更多的遍历的方式&#xff0c;但应该选择那种遍历方式&#xff1f;反而成了一个问题。 本文先从 HashMap 的遍历方法讲起&#xff0c;然后再从性能、原理以及安全性等方面&#xff0c;来分析 HashMap 各种遍历…

C++实践模拟(vector list,反向迭代器)

vector与list的模拟实现&#xff0c;主要是在实现过程中体会模板在编程中的应用&#xff0c;了解C中迭代器的底层实现机制&#xff0c;因此笔者会将重点内容放在模板应用&#xff0c;迭代器底层实现&#xff0c;反向迭代器&#xff0c;迭代器失效等方面&#xff0c;至于其他的增…

Webpack5构建性能优化:构建耗时从150s到60s再到10s | 京东云技术团队

作者&#xff1a;京东科技 牛志伟 近期对Webpack5构建性能进行了优化&#xff0c;构建耗时从150s到60s再到10s&#xff0c;下面详细讲解下优化过程。 优化前现状 1.历史项目基于Vue3 Webpack5技术栈&#xff0c;其中webpack配置项由开发者自己维护&#xff08;没有使用vue/…

有哪些入耳舒服的蓝牙耳机?公认佩戴舒适度最高蓝牙耳机

数码科技市场日益发展的更加智能化&#xff0c;日常所接触到的各类产品都开始往智能化发展&#xff0c;鲜为人知的蓝牙耳机一直是不过时的&#xff0c;因为小巧易携带深受大众喜欢&#xff0c;下面整理了几款佩戴舒适的蓝牙耳机给大家。 一、南卡小音舱Lite2蓝牙耳机 售价&am…

java语法(一)基本概念、数据类型、字符串类、集合、Stream流、异常处理、文件处理IO流

文章目录 java程序运行流程JDK、JRE、JVM的关系 java数据类型基本数据类型基本数据类型之间的转换 引用数据类型数组类Java Bean重写 equals和hashcode 接口接口与类之间的关系 内置包装类装箱和拆箱包装类的应用Object类toString()equals()方法getClass() 方法接收任意引用类型…

QT如何打包生成独立可执行.exe文件

一、将QT程序使用Release编译 二、新建一个文件夹&#xff0c;将Release编译生成的exe文件复制到新建文件夹中 先找到Release编译生成的exe文件夹位置&#xff0c;与项目创建的文件夹有关&#xff1a; 临时文件夹 --》 release --》 xx .exe文件 比如我的这个就是在D:\QtPro\b…

vulnhub-RAVEN:2(MYSQL-UDF提权,手工提权/工具自动提权)

镜像下载&#xff1a;Raven: 2 ~ VulnHub 实验环境&#xff1a;kali&#xff1a;192.168.78.128 vulnhub镜像&#xff1a;192.168.78.133 1、kali查看本地ip地址&#xff0c;以便使用nmap扫描ip段存活主机 2、nmap扫描IP段&#xff0c;发现靶机的IP地址为192.168.78.133 4、访问…

十款常用的Jupyter Notebook Extensions插件

Table of Contents&#xff1a;一款可以在笔记本左侧生成目录树的插件。它可以让你快速导航到不同的章节&#xff0c;也可以折叠和展开各级标题。Codefolding&#xff1a;一款可以折叠代码块的插件。它可以让你隐藏不需要查看的代码&#xff0c;只显示关键的部分&#xff0c;提…

【Linux】Linux的文件操作

文件操作对于不少编程初学者来说都是一件头疼的事情&#xff0c;不管你学习的是什么编程语言&#xff0c;C/C/Java/Go/Python ,因为我们学习到的文件相关的操作对你来说都是一个黑盒&#xff0c;我们只是知道我们应该怎么使用相应的文件函数进行操作&#xff0c;而对于内部的实…

Linux线程同步(2)——初识互斥锁

互斥锁&#xff08;mutex&#xff09;又叫互斥量&#xff0c;从本质上说是一把锁&#xff0c;在访问共享资源之前对互斥锁进行上锁&#xff0c;在访问完成后释放互斥锁&#xff08;解锁&#xff09;&#xff1b;对互斥锁进行上锁之后&#xff0c;任何其它试图再次对互斥锁进行加…

C语言:青蛙跳台与汉诺塔问题

青蛙跳台 原理&#xff1a;一只青蛙跳n个台阶&#xff0c;青蛙可以一次性跳1个台阶&#xff0c;也可以跳2个台阶&#xff0c;问&#xff0c;有多少种跳法&#xff0c;可以跳过n个台阶。 分析&#xff1a;青蛙跳台本质上是递归问题&#xff0c;那它为什么是递归问题呢&#xff…

cleanmymac在哪下载?中文官网安装教程

CleanMyMac是一个系统清理工具&#xff0c;删除系统缓存文件 , 多余的应用程序语言包 , PowerPc软件运行库等。 是个给你的硬盘瘦身的好工具。 系统&#xff1a;macOS 10.14&#xff08;在10.15以及Big Sur中的安装激活教程相同&#xff09;登录CleanMyMac X下载页面&#xff0…

第十四章_缓存双写一致性之更新策略探讨

缓存双写一致性的理解 如果redis中有数据 需要和数据库中的值相同 如果redis中无数据 数据库中的值要是最新值&#xff0c;且准备回写redis 缓存按照操作来分&#xff0c;细分2种 只读缓存 读写缓存 同步直写策略 写数据库后也同步写redis缓存&#xff0c;缓存和数据库…

01:mysql基本操作---DDL

目录 前言: 1:SQL分类 2:类型 3:sql表的创建----简单版本 前言: 1:SQL语句可以单行或多行书写&#xff0c;以分号结尾。 2:SQL语句可以使用空格/缩进来增强语句的可读性。 3:MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写 4:注释: 单行注释:-- 注释…

RabbitMQ详解(三):消息模式(fanout、direct、topic、work)

消费模式 参考官网&#xff1a;https://www.rabbitmq.com/getstarted.html 简单模式 Simple, 参考RabbitMQ详解&#xff08;二&#xff09;&#xff1a;消息模式 Simple(简单)模式 简单模式是最简单的消息模式&#xff0c;它包含一个生产者、一个消费者和一个队列。生产者向队…

量化散户交易数据:追涨爆亏99%,杀跌少赚28倍?| 追涨杀跌一时爽,散户钱包火葬场?【邢不行】

你第一次炒股的经历是不是这样的&#xff1a; 你有一个朋友&#xff0c;他说在XX股票上大赚了一笔&#xff0c;你听后是既羡慕又不服。 于是你下载了炒股软件&#xff0c;看了眼这只股票&#xff0c;有点心动。但由于没有交易经验&#xff0c;股价又确实涨了不少&#xff0c;…

Python基础入门编程代码练习(四)

一、遍历列表 通过 input输入3个人信息&#xff0c;每个人有姓名和年龄&#xff0c;将信息存入字典中&#xff0c;并将将字典存入列表。 遍历列表&#xff0c;打印每个人的信息&#xff0c;打印格式如下&#xff1a; 张三 20李四 22王五 23 1. 输入三个人的信息 (输入 inpu…

qiankun 微前端 demo(Vue2)

前言 这是我最近刚开始学微前端&#xff08;qiankun框架&#xff09;做的一个小demo&#xff0c;做的时候还是遇到很多问题的&#xff0c;在网上也是看了很多别人的Blog&#xff0c;最后也是磨出来了&#x1f602;&#x1f602;&#x1f602;&#xff1b;这篇文章总统分为分为…

国产麒麟操作系统 myCat1.6读写分离

我的环境是麒麟操作系统&#xff0c;我只配置读写分离 一、使用说明&#xff0c;java环境&#xff0c;解压就能用 下载地址https://raw.githubusercontent.com/MyCATApache/Mycat-download/master/1.6-RELEASE/Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz mycat 是j…

OJ刷题之旅

题目 现在给你两种颜色的箩筐&#xff0c;需要的时候&#xff0c;就把一个个大小差一圈的筐叠上去&#xff0c;使得从上往下看时&#xff0c;边筐花色交错。这个工作现在要让计算机来完成&#xff0c;得看你的了。 输入 输入是一个个的三元组&#xff0c;分别是&#xff0c;外…