i.MX6ULL(十六) linux 设备驱动

news2024/11/15 11:16:09

一 简介

Linux设备驱动是指驱动Linux内核与硬件设备进行通信的软件模块。设备驱动通常分为两类:字符设备驱动和块设备驱动。

设备驱动的主要功能包括:

  1. 设备初始化:在系统启动时,设备驱动需要初始化相应的硬件设备,设置设备的寄存器和接口等参数,以确保设备能够正常工作。
  2. 设备控制:设备驱动需要提供一些接口,用于控制设备的各种操作,如打开设备、读取数据、写入数据、关闭设备等。
  3. 中断处理:设备驱动需要处理硬件设备的中断请求,在中断发生时执行相应的中断处理程序,以便及时响应设备的各种事件和请求。
  4. 数据传输:设备驱动需要实现数据的传输功能,包括从硬件设备读取数据和向硬件设备写入数据等操作。
  5. 错误处理:设备驱动需要处理设备发生的错误和异常情况,例如设备读写错误、中断丢失等。

1.1 设备驱动分类

1.1.1 块设备驱动

块设备驱动是指以块为单位与设备进行通信的驱动程序,例如硬盘、固态硬盘、USB闪存驱动器等存储设备驱动

块设备驱动程序通常采用分层结构,包括以下几个层次:

  1. 设备驱动程序层:这一层直接与硬件设备进行通信,并控制设备的各种操作。
  2. 存储卷管理器层:这一层负责管理存储卷(如硬盘分区、逻辑卷等),并提供对卷的访问接口。
  3. 文件系统层:这一层提供对卷的的文件系统接口,使得用户可以按照文件系统的目录结构访问和管理文件。

1.1.2 字符设备驱动

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC SPI
LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

I2C/SPI 设备可以是字符设备、块设备或网络设备,具体取决于其功能和应用场景。

然而,对于某些I2C设备来说,它们可能被归类为块设备或网络设备等其他类型的设备。例如,某些I2C存储器(如EEPROM)可以被视为块设备的例子,因为它们以数据块为单位进行数据传输。而其他一些I2C设备,如温度传感器或光传感器等,则可以被视为网络设备,因为它们以数据包为单位进行数据传输。

因此,并不是所有的I2C/SPI设备都是字符设备,而是根据其功能和应用场景可能被归类为不同类型的设备。不同类型的设备需要不同的驱动程序来实现对它们的访问和控制。

Linux 应用程序对驱动程序的调用如图 40.1.1 所示

Linux 中一切皆为文件,驱动加载成功以后会在“ /dev ”目录下生成一个相应的文件,应
用程序通过对这个名为“ /dev/xxx (xxx 是具体的驱动文件名字 ) 的文件进行相应的操作即可实
现对硬件的操作。比如现在有个叫做 /dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程
序使用 open 函数来打开文件 /dev/led ,使用完成以后使用 close 函数关闭 /dev/led 这个文件。 open 和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led ,那么就使用 write 函数来操 作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取
led 灯的状态,就用 read 函数从驱动中读取相应的状态。
应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。
当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开 /dev/led 这个驱动,因为用
户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空
间“陷入”到内核空间,这样才能实现对底层驱动的操作。

设备驱动原理

2.1 驱动模块的加载和卸载

2.1.1 加载

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启
动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块 (Linux 下模块扩展名为 .ko) ,在
Linux 内核启动以后使用“ insmod ”命令加载驱动模块。在调试驱动的时候一般都选择将其编译
为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。
而且在调试的时候只需要加载或者卸载驱动模块即可
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块:insmodmodprobe

insmod 命令不能解决模块的依赖关系
比如 drv.ko 依赖 first.ko 这个模块,就必须先使用 insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,
insmod drv.ko
modprobe   drv.ko

2.1.2 卸载

驱动模块的卸载使用命令“ rmmod ”即可,比如要卸载 drv.ko
rmmod drv.ko
modprobe -r drv.ko //
使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没
有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。所以对于模块的卸载,还是 推荐使用 rmmod 命令

2.2 地址映射 MMU

MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU ,但是现在
Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性
对于 32 位 的处理器来说,虚拟地址范围是 2^32=4GB
Linux 内核启动的时候会初始化 MMU ,设置好内存映射,设置好以后 CPU 访问的都是虚
拟地址。
比如I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068
如果没有开启 MMU 的话 直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。
现在开启 了 MMU ,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必 须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内 存和虚拟内存之间的转换,需要用到两个函数:ioremap iounmap

2.2.1 ioremap 函数

ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间,定义在 arch/arm/include/asm/io.h 文件中,定义如下:
示例代码 41.1.1.1 ioremap 函数
 #define ioremap(cookie,size) __arm_ioremap((cookie), (size), 
MT_DEVICE)

 void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, 
unsigned int mtype)
 {
 return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
 }
此函 数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr :要映射的物理起始地址。
size :要映射的内存空间大小。
mtype ioremap 的类型,可以选择 MT_DEVICE MT_DEVICE_NONSHARED
MT_DEVICE_CACHED MT_DEVICE_WC ioremap 函数选择 MT_DEVICE
返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。

2.2.2 iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原
型如下
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr ,此参数就是要取消映射的虚拟地址空间首地址。

2.2.3 I/O 内存访问函数

linux通过操作虚拟地址来间接操作物理地址

1 、读操作函数
 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

写操作函数

 void writeb(u8 value, volatile void __iomem *addr)
 void writew(u16 value, volatile void __iomem *addr)
 void writel(u32 value, volatile void __iomem *addr)

2.3 设备号

Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备

2.3.1组成

Linux 提供了 一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面,
dev_t  __u32 类型的其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux
系统中主设备号范围为 0~4095 ,所以大家在选择主设备号的时候一定不要超过这个范围。

2.3.2 设备号分配与注销

静态分配
有些常用的设备号已经被 Linux 内核开发者给分配掉 了,具体分配的内容可以查看文档 Documentation/devices.txt 。并不是说内核开发者已经分配掉 的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个 主设备号,使用“cat /proc/devices ”命令即可查看当前系统中所有已经使用了的设备号。
动态分配
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:
申请函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。 一般 baseminor 为 0,也就是说次设备号从 0 开始。
count 要申请的设备号数量。
注册函数
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
3 注销函数
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
from :要释放的设备号。
count 表示从 from 开始,要释放的设备号数量。

2.4 新的字符设备注册方法

2.4.1、字符设备结构

Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中
的定义如下:
 struct cdev {
 struct kobject kobj;
 struct module *owner;
 const struct file_operations *ops;
 struct list_head list;
 dev_t dev;
 unsigned int count;
 };
cdev 中有两个重要的成员变量: ops dev ,这两个就是字符设备文件操作函数集合
file_operations 以及设备号 dev_t

2.4.2cdev_init 函数

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化, cdev_init 函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
使用 cdev_init 函数初始化 cdev 变量的示例代码如下:

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

	
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};


/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
2.4.3、cdev_add 函数
cdev_add 函数用于向 Linux 系统添加字符设备 (cdev 结构体变量 )
首先使用 cdev_init 函数 完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。
cdev_add 函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数 p 指向要添加的字符设备 (cdev 结构体变量 ) ,参数 dev 就是设备所使用的设备号,参
count 是要添加的设备数量。
2.4.4、cdev_del 函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备, cdev_del
函数原型如下:
void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备。

2.5 自动创建设备节点

在前面的 Linux 驱动实验中,当我们使用 modprobe 加载驱动程序以后还需要使用命令
mknod ”手动创建设备节点。本节就来讲解一下如何实现自动创建设备节点,在驱动中实现
自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在 /dev 目录下
创建对应的设备文件。

2.5.1 mdev 机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检
测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用
modprobe 命令成功加载驱动模块以后就自动在 /dev 目录下创建对应的设备节点文件 , 使用
rmmod 命令卸载驱动模块以后就删除掉 /dev 目录下的设备节点文件。 使用 busybox 构建根文件
系统的时候,busybox 会创建一个 udev 的简化版本—mdev 所以在嵌入式 Linux 中我们使用
mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由 mdev 管理,在
/etc/init.d/rcS 文件中如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
如何通过 mdev 来实现设备文件节点的自动创建与删除?
2.5.1.1 创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添
加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件
include/linux/device.h 里面。 class_create 是类创建函数, class_create 是个宏定义,
struct class *class_create (struct module *owner, const char *name)
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy ,函数原型如下:
void class_destroy(struct class *cls);
2.5.1.2 创建设备
使用 device_create 函数在类下面创建设备
struct device *device_create(struct class
*class,
struct device *parent,
dev_t
devt,
void
*drvdata,
const char
*fmt, ...)
device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父
设备,一般为 NULL ,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用
的一些数据,一般为 NULL ;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成 /dev/xxx
这个设备文件。返回值就是创建好的设备。
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy ,函数原
型如下:
void device_destroy(struct class *class, dev_t devt)

2.6 设置文件私有数据

每个硬件设备都有一些属性,比如主设备号 (dev_t) ,类 (class) 、设备 (device) 、开关状态 (state)
等等,在编写驱动的时候你可以将这些属性全部写成变量的形式,编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中,如下
/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

/*
 * @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 = &newchrled; /* 设置私有数据 */
	return 0;
}

2.7 通用设备驱动创建模版  参考示例

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>




/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* 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 = &newchrled; /* 设置私有数据 */
	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)
{
	
	return 0;
}

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

/* 设备操作函数 */
static struct file_operations newchrled_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)
{
	...

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (newchrled.major) {		/*  定义了设备号 */
		newchrled.devid = MKDEV(newchrled.major, 0);
		register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申请设备号 */
		newchrled.major = MAJOR(newchrled.devid);	/* 获取分配号的主设备号 */
		newchrled.minor = MINOR(newchrled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	
	
	/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		return PTR_ERR(newchrled.class);
	}

	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		return PTR_ERR(newchrled.device);
	}
	
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{

    ....
	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除cdev */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */

	device_destroy(newchrled.class, newchrled.devid);
	class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
// MODULE_VERSION("4.1.15-g3dc0a4b SMP preempt mod_unload modversions ARMv7 p2v8")

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

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

相关文章

人工智能、机器学习、自然语言处理、深度学习等有啥本质的区别?

大众媒体赋予这些术语的含义通常与机器学习科学家和工程师的理解有所出入。因此&#xff0c;当我们使用这些术语时&#xff0c;给出准确的定义很重要&#xff0c;其关系韦恩图如图1.2所示。 图1.2 自然语言处理、人工智能、机器学习和深度学习等术语的关系韦恩图 1 人工智能 …

企业使用CRM后的三大提升

数字化转型是企业发展的重要决策&#xff0c;为此&#xff0c;很多企业使用CRM客户管理系统管理客户数据、优化业务流程&#xff0c;直面数字化浪潮。那么&#xff0c;使用CRM系统后&#xff0c;企业会有哪些变化呢&#xff1f; 一、提升客户体验 使用CRM系统后&#xff0c;企…

第二周周报

PCA PCA 是一种流行的统计技术&#xff0c;用于识别数据集中最重要的特征或模式并将其转换为较低维的表示形式。本报告将涵盖PCA的基本原理、其优点。 步骤 主成分分析是一种数学技术&#xff0c;可降低数据集的维数&#xff0c;同时保留尽可能多的信息。它通过查找主成分来…

Redis集群(Cluster和Codis)

1.为什么使用集群&#xff1f; 当我们遇到一个大数据量存储情况时&#xff0c;有两种解决方案&#xff1a; 纵向扩容&#xff1a;升级单个redis实例的配置&#xff0c;包括增加内存容量、磁盘容量&#xff0c;以及使用更高的cpu配置。 纵向扩容&#xff1a;简单直接。也存在一些…

98英寸带来加倍沉浸享受,三星Q80Z新品呈现大有不同的精彩

作者 | 曾响铃 文 | 响铃说 在过去几年&#xff0c;家电行业可以说是负重前行。在经历2022年整年寒冬后&#xff0c;2023年的彩电市场能否迎来一场翻身仗&#xff1f;这是年初被业内讨论最多的话题。 目前2023年已经过半&#xff0c;据奥维云网数据显示&#xff0c;2023年一…

InsCode Stable Diffusion 美图活动一期——即刻体验!来自 CSDN 的 SD 模型

文章目录 &#x1f525;关于活动&#x1f4cb;前言&#x1f3af;什么是 Stable Diffusion&#x1f9e9;Stable Diffusion 与其他 AI 艺术生成器有什么区别&#xff1f; &#x1f3af;开始体验 InsCode Stable Diffusion&#x1f3af;试用 SD 模型生成优质人物好图&#x1f9e9;…

内存池是什么原理?|内存池简易模拟实现|为学习高并发内存池tcmalloc做准备

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 这两个都是博主在学习Linux操作系统过程中的记录&#xff0c;希望对大家的学习有帮助&#xff01; 操作系统Operating Syshttps://blog.csdn.net/yu_cblog/category_12165502.html?spm1001.2014.3001.5482Linux S…

在Linux系统中如何搭建Apache服务

在Linux系统中如何搭建Apache服务 Apache服务是一种开源的、跨平台的Web服务器软件&#xff0c;它的作用是提供网页内容给用户的Web浏览器。具体来说&#xff0c;Apache服务有以下几个主要的作用&#xff1a; 1.Web服务器&#xff1a;Apache是一款功能强大且广泛使用的Web服务…

54、Mysql索引的数据结构,各自优劣

Mysql索引的数据结构&#xff0c;各自优劣 索引的数据结构和具体存储引擎的实现有关在MySQL中使用较多的索引有Hash索引&#xff0c;B树索引等InnoDB存储引擎的默认索引实现为: B树索引。对于哈希索引来说&#xff0c;底层的数据结构就是哈希表&#xff0c;因此在绝大多数需求…

C++编程(三)—— C++11

文章目录 绑定器和函数对象函数对象绑定器lambda表达式 关键词与语法autonullptr右值引用 智能指针容器set和mapunordered_set和unordered_map数组链表 语言级别支持的多线程编程thread子线程如何结束主线程如何处理子线程线程间的互斥线程的同步通信机制&#xff08;条件变量&…

Windows Cluster 分布式算法

在分布式系统中&#xff0c;都需要解决分布式一致性问题。那么&#xff0c;在Windows 集群中&#xff0c;使用了什么算法来保证集群的一致性呢——Paxos。Windows Server 故障转移集群 (WSFC) 使用 Paxos 算法在整个系统中同步更改。通过记录 Paxos Tag 值并保留历史记录&#…

连锁门店如何搭建B2B2C多用户商城系统?

现在很多的线下店铺都开始慢慢的转型线上了&#xff0c;想线上线下相结合&#xff0c;但是最近很多的商家都在问什么样的B2B2C商城系统开发适合线下店铺呢?这个问题今天加速度jsudo小编给大家一起整理如下&#xff0c;相信商家看完后就知道如何选择一款合适的商城系统了。 一、…

【C语言】求序列前N项和

问题描述 输入一个正整数n&#xff0c;输出2/13/25/38/5…的前n项之和&#xff08;该序列从第2项起&#xff0c;每一项的分子是前一项分子与分母的和&#xff0c;分母是前一项的分子&#xff09;&#xff0c;保留2位小数。试编写相应程序。 代码实现 #include<stdio.h>…

spring-14优化性能

原始Junit测试Spring的问题 这里的&#xff1a;第一行代码获得应用上下文对象&#xff0c;第二行代码获得你那个对象 2、原先测试找junit&#xff0c;现在测试找spring&#xff0c;然后再找&#xff0c;junit&#xff0c;告诉我配置文件是什么&#xff0c;然后注入测试对象&am…

LeetCode 172.阶乘后的零

基础方法就是暴力解&#xff0c;其次是数学归纳。 具体思路如下&#xff1a;&#xff08;copy大佬的&#xff09; 耐心看完&#xff01; 代码如下&#xff1a; #include <iostream> #include <vector> #include <cmath> #include <algorithm> #incl…

Android GridPager实战,从RecyclerView to ViewPager

这个简单的的案例展示了如何从RecyclerView to ViewPager&#xff0c;以网上的公开图片为样例。 安卓开发中从RecyclerView 到 ViewPager demo运行结果demo项目工程目录结构关键代码 MainActivity关键代码GridFragment关键代码ImageFragment关键代码ImagePagerFragment关键布局…

关于示波器引入50HZ工频干扰的解释

前几天&#xff0c;小白在实验室工作时&#xff0c;听到同事抱怨示波器有问题。上前查看&#xff0c;才知道&#xff0c;小白的那位同事在测量信号波形时&#xff0c;实际与理想相差甚远。于是乎&#xff0c;在探头什么也不接的情况下&#xff0c;发现示波器本身也存在波形信号…

2022Robocom国赛-u2 女王的大敕令

副本是游戏里的一个特色玩法&#xff0c;主要为玩家带来装备、道具、游戏资源的产出&#xff0c;满足玩家的游戏进程。 在 MMORPG《最终幻想14》里&#xff0c;有一个攻略人数最大达到 48 人的副本“零式贡希尔德神庙”&#xff0c;其中守关 BOSS “天佑女王”有一个很有趣的技…

图形编辑器开发:一些会用到的简单几何算法

大家好&#xff0c;我是前端西瓜哥。 开发图形编辑器&#xff0c;你会经常要解决一些算法问题。本文盘点一些我开发图形编辑器时遇到的简单几何算法问题。 矩形碰撞检测 判断两个矩形是否发生碰撞&#xff08;或者说相交&#xff09;&#xff0c;即两个矩形有重合的区域。 …

C生万物 | 程序员必备实用调试技巧分享

一起来学习调试~ 一、前言二、什么是Bug&#xff1f;三、调试是什么&#xff1f;有多重要&#xff1f;1、导学引入2、调试的基本步骤3、Debug和Release的介绍 四、Windows环境下VS调试介绍1、调试环境的准备2、学会快捷键3、调试的时候查看程序当前信息3.1 查看临时变量的值3.2…