【Linux-并发与竞争】

Linux-并发与竞争

  • ■ 原子操作
    • ■ 原子操作简介
    • ■ 原子整形操作 API 函数
    • ■ 原子位操作 API 函数
    • ■ 示例一:原子操作实验,使用原子变量来实现对实现设备的互斥访问
  • ■ 自旋锁
    • ■ 自旋锁 API 函数
    • ■ 死锁
    • ■ 最好的解决死锁方法就是获取锁之前关闭本地中断,
    • ■ 自旋锁使用示例
    • ■ 示例一:自旋锁实验,使用自旋锁来实现对实现设备的互斥访问
  • ■ 读写自旋锁
    • ■ API 函数
  • ■ 顺序锁
    • ■ 顺序锁的 API 函数
  • ■ 信号量
    • ■ 信号量的特点:
    • ■ 信号量 API 函数
    • ■ 示例一:简单使用方法
    • ■ 示例二:信号量实验,使用信号量来实现对实现设备的互斥访问
  • ■ 互斥体 (mutex)
    • ■ mutex 结构体表示互斥体
    • ■ 使用 mutex 的时候要注意如下几点:
    • ■ 互斥体 API 函数
    • ■ 示例一:互斥体使用示例
    • ■ 示例二:互斥体实验,使用互斥体来实现对实现设备的互斥访问

■ 原子操作

原子变量 lock,用来实现一次只能允许一个应用访问 LED 灯。
atomic_inc 释放 lcok,
原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。

■ 原子操作简介

a = 3  这一行 C语言可能会被编译为如下所示的汇编代码:
ldr r0, =0X30000000 /* 变量 a 地址 */
ldr r1, = 3 /* 要写入的值 */
str r1, [r0] /* 将 3 写入到 a 变量中 */

在这里插入图片描述
在这里插入图片描述

■ 原子整形操作 API 函数

ypedef struct {int counter;  
} atomic_t;typedef struct {long long counter;     // Linux 内核也定义了 64 位原子结构体
} atomic64_t;

atomic_t a; //定义 a
也可以在定义原子变量的时候给原子变量赋初值,如下所示:
atomic_t b = ATOMIC_INIT(0); //定义原子变量 b 并赋初值为 0

函数描述
ATOMIC_INIT(int i)定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v)读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i)向 v 写入 i 值。
void atomic_add(int i, atomic_t *v)给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v)从 v 减去 i 值。
void atomic_inc(atomic_t *v)给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v)从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v)从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v)给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v)从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v)从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v)给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v)给 v 加 i,如果结果为负就返回真,否则返回假
atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */
atomic_set(&v, 10); /* 设置 v=10 */
atomic_read(&v); /* 读取 v 的值,肯定是 10 */
atomic_inc(&v); /* v 的值加 1, v=11 */

■ 原子位操作 API 函数

函数描述
void set_bit(int nr, void *p)将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p)将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p)将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p)获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p)将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p)将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p)将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

■ 示例一:原子操作实验,使用原子变量来实现对实现设备的互斥访问

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */int led_gpio;			/* led所使用的GPIO编号		*/atomic_t lock;			/* 原子变量 */
};struct gpioled_dev gpioled;	/* 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)
{/* 通过判断原子变量的值来检查LED有没有被别的应用使用 */if (!atomic_dec_and_test(&gpioled.lock)) {atomic_inc(&gpioled.lock);	/* 小于0的话就加1,使其原子变量等于0 */return -EBUSY;				/* LED被使用,返回忙 */}filp->private_data = &gpioled; /* 设置私有数据 */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)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {	gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data;/* 关闭驱动文件的时候释放原子变量 */atomic_inc(&dev->lock);return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_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)
{int ret = 0;/* 初始化原子变量 */atomic_set(&gpioled.lock, 1);	/* 原子变量初始值为1 *//* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;} else {printk("gpioled node find!\r\n");}/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) {		/*  定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);} else {						/* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	/* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {return PTR_ERR(gpioled.class);}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {return PTR_ERR(gpioled.device);}return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

结果是当一个程序在访问 IO 时,在打开就会失败。

■ 自旋锁

spinlock_t lock; /* 自旋锁 *

①、自旋锁保护的临界区要尽可能的短,因此在 open 函数中申请自旋锁,然后在 release 函数中释放自旋锁的方法就不可取。
我们可以使用一个变量来表示设备的使用情况,如果设备被使用了那么变量就加一,设备被释放以后变量就减 1,我们只需要使用自旋锁保护这个变量即可。
②、考虑驱动的兼容性,合理的选择 API 函数。

我们通过定义一个变量 dev_stats 表示设备的使用情况,
dev_stats为 0 的时候表示设备没有被使用,
dev_stats 大于 0 的时候表示设备被使用。

驱动 open 函数中先判断 dev_stats 是否为 0,也就是判断设备是否可用,如果为 0 的话就使用设备,并且将 dev_stats加 1,表示设备被使用了。使用完以后在 release 函数中将 dev_stats 减 1,表示设备没有被使用了。因此真正实现设备互斥访问的是变量 dev_stats,但是我们要使用自旋锁对 dev_stats 来做保护。

■ 自旋锁 API 函数

函数描述
DEFINE_SPINLOCK(spinlock_t lock)定义并初始化一个自选变量。
int spin_lock_init(spinlock_t *lock)初始化自旋锁。
void spin_lock(spinlock_t *lock)获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock)释放指定的自旋锁。
int spin_trylock(spinlock_t *lock)尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t *lock)检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0。

■ 死锁

在这里插入图片描述

■ 最好的解决死锁方法就是获取锁之前关闭本地中断,

Linux 内核提供了相应的 API 函数,

函数描述
void spin_lock_irq(spinlock_t *lock)禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock)激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags)保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

■ 自旋锁使用示例

DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 *//* 线程 A */
void functionA (){unsigned long flags; /* 中断状态 */spin_lock_irqsave(&lock, flags) /* 获取锁 *//* 临界区 */spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}/* 中断服务函数 */
void irq() {spin_lock(&lock) /* 获取锁 *//* 临界区 */spin_unlock(&lock) /* 释放锁 */
}

■ 示例一:自旋锁实验,使用自旋锁来实现对实现设备的互斥访问

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */int led_gpio;			/* led所使用的GPIO编号		*/int dev_stats;			/* 设备使用状态,0,设备未使用;>0,设备已经被使用 */spinlock_t lock;		/* 自旋锁 */
};struct gpioled_dev gpioled;	/* 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)
{unsigned long flags;filp->private_data = &gpioled; /* 设置私有数据 */spin_lock_irqsave(&gpioled.lock, flags);	/* 上锁 */if (gpioled.dev_stats) {					/* 如果设备被使用了 */spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */return -EBUSY;}gpioled.dev_stats++;	/* 如果设备没有打开,那么就标记已经打开了 */spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */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)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {	gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{unsigned long flags;struct gpioled_dev *dev = filp->private_data;/* 关闭驱动文件的时候将dev_stats减1 */spin_lock_irqsave(&dev->lock, flags);	/* 上锁 */if (dev->dev_stats) {dev->dev_stats--;}spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_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)
{int ret = 0;/*  初始化自旋锁 */spin_lock_init(&gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;} else {printk("gpioled node find!\r\n");}/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) {		/*  定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);} else {						/* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	/* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {return PTR_ERR(gpioled.class);}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {return PTR_ERR(gpioled.device);}return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

■ 读写自旋锁

读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个线程写锁写操作,而且不能进行读操作。
但是当没有写操作的时候允许一个或多个线程持有读锁,可以进行并发的读操作。
Linux 内核使用 rwlock_t 结构体表示读写锁,

typedef struct {arch_rwlock_t raw_lock;
} rwlock_t;

■ API 函数

函数描述
DEFINE_RWLOCK(rwlock_t lock)定义并初始化读写锁
void rwlock_init(rwlock_t *lock)初始化读写锁。
读锁
void read_lock(rwlock_t *lock)获取读锁。
void read_unlock(rwlock_t *lock)释放读锁。
void read_lock_irq(rwlock_t *lock)禁止本地中断,并且获取读锁。
void read_unlock_irq(rwlock_t *lock)打开本地中断,并且释放读锁。
void read_lock_irqsave(rwlock_t *lock,unsigned long flags)保存中断状态,禁止本地中断,并获取读锁。
void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。
void read_lock_bh(rwlock_t *lock)关闭下半部,并获取读锁。
void read_unlock_bh(rwlock_t *lock)打开下半部,并释放读锁。
写锁
void write_lock(rwlock_t *lock)获取写锁。
void write_unlock(rwlock_t *lock)释放写锁。
void write_lock_irq(rwlock_t *lock)禁止本地中断,并且获取写锁。
void write_unlock_irq(rwlock_t *lock)打开本地中断,并且释放写锁。
void write_lock_irqsave(rwlock_t *lock,unsigned long flags)保存中断状态,禁止本地中断,并获取写锁。
void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放读锁。
void write_lock_bh(rwlock_t *lock)关闭下半部,并获取读锁。
void write_unlock_bh(rwlock_t *lock)打开下半部,并释放读锁。

■ 顺序锁

使用读写锁的时候读操作和写操作不能同时进行
使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作。

■ 顺序锁的 API 函数

函数描述
DEFINE_SEQLOCK(seqlock_t sl)定义并初始化顺序锁
void seqlock_ini seqlock_t *sl)初始化顺序锁。
顺序锁写操作
void write_seqlock(seqlock_t *sl)获取写顺序锁。
void write_sequnlock(seqlock_t *sl)释放写顺序锁。
void write_seqlock_irq(seqlock_t *sl)禁止本地中断,并且获取写顺序锁
void write_sequnlock_irq(seqlock_t *sl)打开本地中断,并且释放写顺序锁。
void write_seqlock_irqsave(seqlock_t *sl,unsigned long flags)保存中断状态,禁止本地中断,并获取写顺序锁。
void write_sequnlock_irqrestore(seqlock_t *sl,unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放写顺序锁。
void write_seqlock_bh(seqlock_t *sl)关闭下半部,并获取写读锁。
void write_sequnlock_bh(seqlock_t *sl)打开下半部,并释放写读锁。
顺序锁读操作
unsigned read_seqbegin(const seqlock_t *sl)读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号。
unsigned read_seqretry(const seqlock_t *sl,unsigned start)读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读

■ 信号量

某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这100 个停车位就是共享资源。
当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,
当有车开出停车场的时候停车数量就会减一,也就是说信号量减一
你把车停进去以后停车数量就会加一,也就是信号量加一

比如 A 与 B、 C 合租了一套房子,这个房子只有一个厕所,一次只能一个人使用。
某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。
B 要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。 B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继
续回房间睡觉,这个时候相当于信号量。

■ 信号量的特点:

①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

■ 信号量 API 函数

Linux 内核使用 semaphore 结构体表示信号量,

struct semaphore {raw_spinlock_t lock;unsigned int count;struct list_head wait_list;
};
函数描述
DEFINE_SEAMPHORE(name)定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val)初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。
而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem)释放信号量

■ 示例一:简单使用方法

struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */

■ 示例二:信号量实验,使用信号量来实现对实现设备的互斥访问

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT 1		   /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0			   /* 关灯 */
#define LEDON 1				   /* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev
{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node *nd; /* 设备节点 */int led_gpio;			/* led所使用的GPIO编号		*/struct semaphore sem;	/* 信号量 */
};struct gpioled_dev gpioled; /* 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 = &gpioled; /* 设置私有数据 *//* 获取信号量 */if (down_interruptible(&gpioled.sem)){ /* 获取信号量,进入休眠状态的进程可以被信号打断 */return -ERESTARTSYS;}
#if 0down(&gpioled.sem);		/* 不能被信号打断 */
#endifreturn 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)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0){printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 获取状态值 */if (ledstat == LEDON){gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */}else if (ledstat == LEDOFF){gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data;up(&dev->sem); /* 释放信号量,信号量值加1 */return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_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)
{int ret = 0;/* 初始化信号量 */sema_init(&gpioled.sem, 1);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if (gpioled.nd == NULL){printk("gpioled node not find!\r\n");return -EINVAL;}else{printk("gpioled node find!\r\n");}/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);if (gpioled.led_gpio < 0){printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if (ret < 0){printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major){ /*  定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);}else{																	   /* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */gpioled.major = MAJOR(gpioled.devid);							   /* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid);							   /* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n", gpioled.major, gpioled.minor);/* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)){return PTR_ERR(gpioled.class);}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)){return PTR_ERR(gpioled.device);}return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);							  /*  删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

■ 互斥体 (mutex)

虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。
互斥访问表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。

■ mutex 结构体表示互斥体

struct mutex {/* 1: unlocked, 0: locked, negative: locked, possible waiters */atomic_t count;spinlock_t wait_lock;
};

■ 使用 mutex 的时候要注意如下几点:

①、 mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
②、和信号量一样, mutex 保护的临界区可以调用引起阻塞的 API 函数。
③、因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。

■ 互斥体 API 函数

函数描述
DEFINE_MUTEX(name)定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock)初始化 mutex。
void mutex_lock(struct mutex *lock)获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock)释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock)尝试获取 mutex,如果成功就返回 1,如果失败就返回 0。
int mutex_is_locked(struct mutex *lock)判断 mutex 是否被获取,如果是的话就返回1,否则返回 0。
int mutex_lock_interruptible(struct mutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。

■ 示例一:互斥体使用示例

struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
/* 临界区 */
mutex_unlock(&lock); /* 解锁 */

■ 示例二:互斥体实验,使用互斥体来实现对实现设备的互斥访问

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 *//* gpioled设备结构体 */
struct gpioled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */int led_gpio;			/* led所使用的GPIO编号		*/struct mutex lock;		/* 互斥体 */
};struct gpioled_dev gpioled;	/* 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 = &gpioled; /* 设置私有数据 *//* 获取互斥体,可以被信号打断 */if (mutex_lock_interruptible(&gpioled.lock)) {return -ERESTARTSYS;}
#if 0mutex_lock(&gpioled.lock);	/* 不能被信号打断 */
#endifreturn 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)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;struct gpioled_dev *dev = filp->private_data;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {	gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */} else if(ledstat == LEDOFF) {gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{struct gpioled_dev *dev = filp->private_data;/* 释放互斥锁 */mutex_unlock(&dev->lock);return 0;
}/* 设备操作函数 */
static struct file_operations gpioled_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)
{int ret = 0;/* 初始化互斥体 */mutex_init(&gpioled.lock);/* 设置LED所使用的GPIO *//* 1、获取设备节点:gpioled */gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL) {printk("gpioled node not find!\r\n");return -EINVAL;} else {printk("gpioled node find!\r\n");}/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);if(gpioled.led_gpio < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio);/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(gpioled.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}/* 注册字符设备驱动 *//* 1、创建设备号 */if (gpioled.major) {		/*  定义了设备号 */gpioled.devid = MKDEV(gpioled.major, 0);register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);} else {						/* 没有定义设备号 */alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */}printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	/* 2、初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);/* 4、创建类 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {return PTR_ERR(gpioled.class);}/* 5、创建设备 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {return PTR_ERR(gpioled.device);}return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);class_destroy(gpioled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

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

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

相关文章

LeetCode 124 —— 二叉树中的最大路径和

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 二叉树的问题首先我们要想想是否能用递归来解决&#xff0c;本题也不例外&#xff0c;而递归的关键是找到子问题。 我们首先来看看一棵最简单的树&#xff0c;也就是示例 1。这样的一棵树总共有六条路径&#xf…

docker如何拉取nginx最新镜像并运行

要拉取Docker Hub上的最新Nginx镜像&#xff0c;您可以使用以下命令&#xff1a; docker pull nginx 这个命令会从Docker Hub下载最新版本的Nginx镜像。如果您想要拉取特定版本的Nginx镜像&#xff0c;可以指定版本号&#xff0c;例如&#xff1a; docker pull nginx:1.18.0 拉…

详细分析tcping的基本知识以及用法

目录 前言1. 安装配置2. 基本知识3. Demo 前言 针对ping的基本知识推荐阅读&#xff1a;详细分析ping的基本知识以及常见网络故障的诊断&#xff08;图文解析&#xff09; 1. 安装配置 针对Window的下载如下&#xff1a; 安装路径&#xff1a;tcping官网 下载tcping.exe&a…

《微服务王国的守护者:Spring Cloud Dubbo的奇幻冒险》

5. 经典问题与解决方案 5.3 服务追踪与链路监控 在微服务架构的广袤宇宙中&#xff0c;服务间的调用关系错综复杂&#xff0c;如同一张庞大的星系网络。当一个请求穿越这个星系&#xff0c;经过多个服务节点时&#xff0c;如何追踪它的路径&#xff0c;如何监控整个链路的健康…

VUE3 学习笔记(3):VUE模板理念、属性绑定、条件渲染、列表渲染

准备 1.清空不必要的项目文件 项目/src/assets/ 目录文件清空 项目/src/components/ 目录文件清空 删除main.js 的css引用 App.vue 代码如下 <template> </template> <script>//注意这里默认有一个setup 去掉 </script> 运行一下无错误提示就可以了…

Cohere继Command-R+之后发布大模型Aya-23,性能超越 Gemma、Mistral 等,支持中文

前言 近年来&#xff0c;多语言大模型&#xff08;MLLM&#xff09;发展迅速&#xff0c;但大多数模型的性能依然存在显著差距&#xff0c;尤其是在非英语语言方面表现不佳。为了推动多语言自然语言处理技术的发展&#xff0c;Cohere团队发布了新的多语言指令微调模型家族——…

机器学习预测-CNN手写字识别

介绍 这段代码是使用PyTorch实现的卷积神经网络&#xff08;CNN&#xff09;&#xff0c;用于在MNIST数据集上进行图像分类。让我一步步解释&#xff1a; 导入库&#xff1a;代码导入了必要的库&#xff0c;包括PyTorch&#xff08;torch&#xff09;、神经网络模块&#xff0…

shell脚本实战--批量修改文件名

字符串截取 先来了解一下shell字符串相关操作的变量 # 从开头删除匹配最短 ## 从开头删除匹配最长 % 从结尾削除匹配最短 %% 从结尾删除匹配最长#指定字符内容截取 a*c 匹配开头为a&#xff0c;中间任意个字符&#xff0c;结尾为c的字符串 a*C 匹配…

Java—集合Collection(一)

Java—集合Collection&#xff08;一&#xff09; 一、Collection集合1、方法add、addAll2、声明集合特别注意1&#xff1a;添加对象时需要创建对象类 3、总结4、判断方法4.1、总结 5、删除6、总结7、集合的其他方法8、所有代码演练 存放单个数据内容&#xff0c;声明一个变量&…

netcat一键开始瑞士军刀模式(KALI工具系列六)

目录 1、KALI LINUX简介 2、netcat工具简介 3、在KALI中使用netcat 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 测试某IP的端口是否打开 4.2 TCP扫描 4.3 UDP扫描 4.4 端口刺探 4.5 直接扫描 5、即时通信 5.1 单击对话互联 5.2 传…

新能源锂电池行业创业的财富方案,锂电池回收高阶课

课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/89292234 更多资源下载&#xff1a;关注我。 实战攻略 12年锂电池回收行业经验与坑全收录 课程内容&#xff1a; 001-课程介绍.mp4 002-锂电池的全种类认识.mp4 003-废品锂电池到级片粉末价值估算,mp…

Go微服务: Http服务注册在Consul的示例(非Go-Micro)

概述 现在&#xff0c;我们使用consul客户端的api来把Http服务注册到consul上&#xff0c;非Go-Micro的形式其实&#xff0c;consul官方提供了对应的接口调用来实现&#xff0c;golang中的consul/api包对其进行了封装我们使用consul/api来进行展示 目录结构 gitee.com/go-mi…

设计模式7——建造者模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 建造者模式&#xff08;Builde…

VMware ESXi 7.0 U3q 发布 - 领先的裸机 Hypervisor

VMware ESXi 7.0 U3q 发布 - 领先的裸机 Hypervisor VMware ESXi 7.0 Update 3 Standard & All Custom Image for ESXi 7.0U3 Install CD 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-esxi-7-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出…

3小时-入门短视频创作:短视频创作入门必修(15节视频课)

课程目录 1、先导课.mp4 2、建立视听思维.mp4 3、口语化.mp4 4、具象化.mp4 5、建立选题思维.mp4 6、2个小白好上手的选题技巧.mp4 7、建立开场思维.mp4 8、3个口播视频方能开场套路.mp4 9、建立脚本结构思维.mp4 10、爆款口指的3大结构.mp4 11、建立标题思维.mp4 …

https为何安全?

HTTPS&#xff08;超文本传输安全协议&#xff09;是一种用于安全通信的网络协议&#xff0c;它在HTTP协议的基础上通过SSL/TLS&#xff08;安全套接层/传输层安全&#xff09;协议来加密数据&#xff0c;以保护网络数据的传输安全。 TLS/SSL 基础概念 概念源自百度百科&…

内存的基本知识与连续分配管理

目录 一. 内存的基础知识1.1. 什么是内存1.2 指令的工作原理1.2.1 装入的三种方式 - 绝对装入1.2.2 可重定位装入1.2.3 动态重定位装入1.3 从写程序到程序运行 二. 基本内存管理的概念三. 覆盖与交换3.1 覆盖技术3.2 交换技术 四. 连续分配管理方式4.1 单一连续分配4.2 固定分区…

【全开源】知识答题系统源码小程序(FastAdmin+ThinkPHP+Unipp)

打造智慧问答的基石 一、引言&#xff1a;知识答题系统的时代意义 在信息爆炸的时代&#xff0c;知识答题系统源码作为一种高效、有趣的知识传播方式&#xff0c;受到了越来越多人的关注和喜爱。通过搭建知识答题系统&#xff0c;我们可以将海量的知识内容以答题的形式展现给…

网络协议——Modbus-TCP

目录 1、简介 2、Modbus-TCP与Modbus-RTU的区别 3、消息格式 4、功能码01H 5、功能码02H 6、功能码03H 7、功能码04H 8、功能码05H 9、功能码06H 10、功能码0FH 11、功能码10H 1、简介 Modbus-TCP&#xff08;Modbus Transmission Control Protocol&#xff09;是一…

【linux】g++/gcc编译器

目录 背景知识 gcc如何完成 预处理(进行宏替换) 编译&#xff08;生成汇编&#xff09; 汇编&#xff08;生成机器可识别代码&#xff09; 链接&#xff08;生成可执行文件或库文件&#xff09; 在这里涉及到一个重要的概念:函数库 函数库一般分为静态库和动态库两…