使用linux系统提供的信号量集和共享内存实现生产者和消费者问题

2023-05-16

使用linux系统提供的信号量集和共享内存实现生产者和消费者问题

实验目的
了解和熟悉linux系统下的信号量集和共享内存。

实验任务
使用linux系统提供的信号量集和共享内存实现生产者和消费者问题。

实验要求
1.写两个程序,一个模拟生产者过程,一个模拟消费者过程;
2.创建一个共享内存模拟生产者-消费者问题中缓冲队列,该缓冲队列有N(例如N=10)个缓冲区,每个缓冲区的大小为1024B,每个生产者和消费者对缓冲区必须互斥访问;
3.由第一个生产者创建信号量集和共享内存,其他生产者和消费者可以使用该信号量集和共享内存;
4.生产者程序:生产者生产产品(即是从键盘输入长度小于1024B的字符)放入空的缓冲区;
5.消费者程序:消费者消费产品(即从满的缓冲区中取出内容在屏幕上打印出来),然后满的缓冲区变为空的缓冲区;
6.多次运行生产者程序和消费者进程,可以产生多个生产者进程和多个消费者进程,这些进程可以共享这些信号量和共享内存,实现生产者和消费者问题;
7.在生产者程序程序中,可以选择:
①生产产品;
②退出。退出进程,但信号量和共享内存仍然存在,其他生产者进程和消费者进程还可以继续使用;
③删除信号量和共享内存。显性删除信号量和共享内存,其他生产者进程和消费者进程都不能使用这些信号量和共享内存。
8.在消费者程序中,可以选择:
①消费产品;
②退出。退出进程,但信号量和共享内存仍然存在,其他生产者进程和消费者进程还可以继续使用;
③删除信号量和共享内存。显性删除信号量和共享内存,其他生产者进程和消费者进程都不能使用这些信号量和共享内存。

一、实验分析
生产者和消费者之间的关系:
(1)对缓冲区的访问是互斥的。
两者都会修改缓冲区,因此,一方修改缓冲区时,另一方不能修改,这就是互斥。
(2)一方的行为影响另一方。
缓冲区不空,才能消费,何时不空?生产了就不空;缓冲区满,就不能生产,何时不满?消费了就不满。这是同步关系。

要满足这两个要求,共需要三个信号量:
第一个信号量用于限制生产者必须在缓冲区不满时才能生产,是同步信号量(empty)
第二个信号量用于限制消费者必须在缓冲区有产品时才消费,是同步信号量(full)
第三个信号量用于限制生产者和消费者在访问缓冲区时必须互斥,是互斥信号量(mutex)

信号量集:
1.创建和访问一个信号量集
int semid = semget(key_t key, int nsems, int semflg);
返回值:正确返回信号量集的标识符,错误时返回-1。
key: 信号量集的标识(保持值相同,则不同进程打开同一个信号量集)
nsems:指定打开或者新创建的信号量集将包含的信号量的数目
semflg:
(1)只设置IPC_CREAT位,则创建一个信号量集,如果该信号量集已经存在,则返回其标识符
(2)设置IPC_CREAT|IPC_EXCL位,则创建一个新的信号量集,如果该key值的信号量已经存在,则返回错误信息。
如,创建一个只包含一个信号量的信号量集:
int semid = semget((key_t)SEM_KEY,1,IPC_CREAT|0666|IPC_EXCL);

2.对信号量的PV操作
int semop(int semid ,struct sembuf * sops ,unsigned nsops);
返回值:正确返回0,错误时返回-1
semid:是信号量集的标识符,由semget()得到
sops:指向一个sembuf结构数组,该数组的每一个元素对应一次信号量操作

struct sembuf {
	unsigned short sem_num;//semaphore index in array
							//标明它是信号量集的第几个元素,从0开始
	short sem_op;               /*semaphore operation*/ 
	short sem_flg;              /*operation flags,操作的执行模式*/
};

其中sembuf结构:
sem_op:对指定信号量采取的操作

sem_op<0相当于P操作,占有资源
sem_op>0相当于V操作,释放资源
sem_op=0进程睡眠直到信号量的值为0

nsops:进行操作信号量的个数,最常见设置此值等于1,只完成对一个信号量的操作

3.信号量集的控制函数
int semctl(int semid ,int semnum ,int cmd ,union semun arg);
返回值:正确时根据cmd的的不同返回值或0,错误时返回-1。
semid:是信号量集的标识符,由semget()得到
semnum:指定semid信号量集的第几个信号量,在撤销信号量集时,此参数可缺省。
cmd:指定操作类型。实验中使用:
SETVAL:指定semval域值为arg.val(指定信号的值为arg.val的值)
GETVAL:返回semnum指定的信号量的semval域值(得到指定信号的值)
IPC_RMID:删除指定信号量集。
如,撤销信号量集 semctl(semid ,IPC_RMID ,0)

③共享内存
1.共享存储是进程间通信的一种手段。共享内存并未提供同步机制,也就是说,
在一个服务进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。共享内存必须自己考虑同步问题,通常使用信号量同步或互斥访问共享存储。
2.共享内存的生命周期随内核,即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象)
3.本实验中,使用共享内存代表缓冲区,互斥信号量控制对缓冲区的访问。共享存储区的原理是将进程的地址空间映射到一个共享存储段。
与共享内存有关的所有函数共用头文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

1.创建共享内存或获得一块共享内存区
int shmid = shmget(key_t key, size_t size, int shmflg);
返回值:成功返回共享内存的描述符,出错返回-1
key:长整型(唯一、非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个id值。通常情况下,该id值通过ftok()得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。
size:指定共享内存的大小
shmflg:是一组标志
(1)只设置IPC_CREAT标志位,则创建一个共享内存段,如果该共享内存段已经存在,则返回其标识符
(2)设置IPC_CREAT|IPC_EXCL标志位,则可以创建一个新的、唯一的共享内存,如果共享内存已存在,返回一个错误。

2.挂接操作(将一共享存储区附接到进程的虚地址空间)
void * viraddr = shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id:由shmget()返回的共享内存标识
shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shm_flg:是一组标志位,通常为0

3.操作共享内存(设置共享存储区的有关的参数)
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shm_id:由shmget()返回的共享内存标识
cmd:是要采取的操作,本实验中使用IPC_RMID(删除共享内存段)
buf:指向共享内存模式和访问权限的结构,本实验中设置为0
如,释放共享内存shmctl(shmid,IPC_RMID,0);

4.分离操作(把共享的内存区域和它自己的地址空间分离开)
int shmdt(const void *shmaddr);
shmaddr:调用shmat时的返回值
注意,该分离操作不从系统中删除标识符和其数据结构,要显示调用shmctl(带命令IPC_RMID)才能删除共享内存区

二、实验过程与结果
①生产者过程原型:

void producer(){
	while(1){
		item = produce_item();	//生产新的数据项
		wait(empty);
		wait(mutex);			//进入临界区
		Buffer[in] = item;		//新生产的数据项放入缓冲区
		in = (in + 1)%N;				
		signal(mutex);			//离开临界区
		signal(full);				//增加已用缓冲区的数目
	}
}

消费者过程原型:

void consumer(){
	while(1){
		wait(full);			
		wait(mutex);			//进入临界区
		item = Buffer(out);		//新生产的数据项放入缓冲区
		out = (out + 1)%N;			
		signal(mutex);			//离开临界区
		signal(empty);			//增加空闲缓冲区的数目
		consume_item(item);	//处理取出的数据项
	}
}

②程序代码
1.生产者过程(Producer.c)

//Producer.c

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/shm.h>

#include <sys/ipc.h>
#include <errno.h>

#define BUF_SIZ 1024  //每个缓冲区的大小
#define N 10		//有N(例如N=10)个缓冲区

#define SEM_KEY_1 1
#define SEM_KEY_2 2
#define SEM_KEY_3 3
//#define SHM_KEY 4

union semun {//共同体类型semun
	int val;//使用的值
	struct semid_ds *buf;   //使用的缓存区,struct semid_ds信号量集的数据结构,在头文件 <sys/sem.h>
	unsigned short * array;//使用的数组
}arg;//该联合体没有定义在任何系统头文件中,因此得用户自己声明

int emptyid;//同步信号量(empty),限制生产者必须在缓冲区空时才生产
int fullid; //同步信号量(full),限制消费者必须在缓冲区有产品时才消费

int mutexid;//互斥信号量(mutex),限制生产者和消费者在访问缓冲区i时必须互斥

//共享内存区结构
struct shared_use_st {
	int Index[N]; //为0,表示对应的缓冲区i未被生产者使用,可生产但不可消费;为1,表示对应的缓冲区i已被生产者使用,不可分配但可消费
	char Buffer[N][BUF_SIZ];    //该缓冲队列有N个字符串缓冲区,每个缓冲区的大小为BUF_SIZ
};


int main() {
	/*****申请只有一个信号量的信号量集*****/
	emptyid = semget((key_t)SEM_KEY_1, 1, IPC_CREAT | 0666 | IPC_EXCL);//创建和访问一个信号量集,第二个参数为信号量的个数,
	fullid = semget((key_t)SEM_KEY_2, 1, IPC_CREAT | 0666 | IPC_EXCL);//创建一个只包含一个信号量的信号量集,即蜕化为一般的记录型信号量或互斥信号量 
	// IPC_CREAT|IPC_EXCL 创建一个新的信号量集,如果该值的信号量已经存在,则返回错误信息,(0666为IPC对象存取权限)
	mutexid = semget((key_t)SEM_KEY_3, 1, IPC_CREAT | 0666 | IPC_EXCL);//申请互斥信号量if (emptyid == -1) {//错误时返回-1if (errno == EEXIST) {//打开已有的信号量
​			emptyid = semget((key_t)SEM_KEY_1, 1, IPC_CREAT | 0666);//打开已有的信号量
​			fullid = semget((key_t)SEM_KEY_2, 1, IPC_CREAT | 0666);//只设置IPC_CREAT位,则创建一个信号量集,如果该信号量集已经存在,则返回其标识符
​			mutexid = semget((key_t)SEM_KEY_3, 1, IPC_CREAT | 0666);}}else {//需要赋初值/*分别对每个信号量赋初值*/
​		arg.val = N;//同步信号量empty的初值为Nif (semctl(emptyid, 0, SETVAL, arg) == -1)perror("semctl setval error");//SETVAL指定信号量的初值为arg.val
​		arg.val = 0;//同步信号量full的初值为0if (semctl(fullid, 0, SETVAL, arg) == -1)perror("semctl setval error");//SETVAL指定信号量的初值为arg.val
​		arg.val = 1;//互斥信号量mutex的初值为1if (semctl(mutexid, 0, SETVAL, arg) == -1)perror("semctl setval error");//SETVAL指定信号量的初值为arg.val}/*定义PV操作*/struct sembuf P, V;
​	P.sem_num = 0;//信号量集的第0个元素
​	P.sem_op = -1;//<0相当于P操作,占有资源
​	P.sem_flg = SEM_UNDO;//指明内核为信号量操作保留恢复值
​	V.sem_num = 0;//信号量集的第0个元素
​	V.sem_op = 1;//>0相当于V操作,释放资源
​	V.sem_flg = SEM_UNDO;//指明内核为信号量操作保留恢复值/*****申请或访问共享内存空间*****/int shmid;//共享内存标识符     
​	shmid = shmget((key_t)1121, sizeof(struct shared_use_st), 0666 | IPC_CREAT | IPC_EXCL);//获得或创建一个共享内存标识符,要想让两个进程看到同一个信号集,只需设置第一个值不变就可以//IPC_CREAT|IPC_EXCL创建一个新的、唯一的共享内存,如果共享内存已存在,返回一个错误 if (shmid == -1) {//获得共享内存空间的标识符if (errno == EEXIST) {//共享内存已存在
​			shmid = shmget((key_t)1121, sizeof(struct shared_use_st), 0666 | IPC_CREAT);}else return -1;}/*将共享内存连接到当前进程的地址空间*/void * shm = NULL;//分配的共享内存的原始首地址
​	shm = shmat(shmid, (void*)0, 0); //返回共享内存的原始首地址if (shm == (void*)-1)exit(EXIT_FAILURE);//失败printf("Memory attached at %ld\n", (intptr_t)shm);struct shared_use_st * viraddr = NULL;//连接到该进程的虚地址空间,即应指向shm
​	viraddr = (struct shared_use_st*)shm;//设置共享内存,viraddr连接到该进程的虚地址空间if (semctl(emptyid, 0, GETVAL) == N) {//empty==N,即缓冲区都可分配printf("初始化共享内存\n");for (int i = 0; i < N; i++)viraddr->Index[i] = 0;}char buffer[BUF_SIZ];        //用于保存输入的文本int i = 0;while (1) {for (i = 0; i < N && viraddr->Index[i] == 1; i++);//找到可被生产者使用的缓冲区Buffer[i] //Index为1表示缓冲区i被消费者占用if (i == N) {//当N个缓冲区都被消费者占用时输出“waiting...”sleep(1);        //sleep一段时间,再次进入循环printf("Waiting...\n");}else {//缓冲区Buffer[i]未被消费者占用semop(emptyid, &P, 1);//最后一个参数值为1,只完成对一个信号量的操作  wait(empty)semop(mutexid, &P, 1);//最后一个参数值为1,只完成对一个信号量的操作  wait(mutex)for (i = 0; i < N && viraddr->Index[i] == 1; i++);//找到可被生产者使用的缓冲区Buffer[i] //Index为1表示缓冲区i被消费者占用 //puts("Enter your text:");fgets(buffer, BUF_SIZ, stdin);//读取stdin字符流最多BUF_SIZ个,并存在buffer数组中strcpy(viraddr->Buffer[i%N], buffer);//读取的字符串存入缓冲区viraddr->Buffer[i]中
​			viraddr->Index[i%N] = 1;//表示该缓冲区被生产者使用了semop(fullid, &V, 1);//signal(full)semop(mutexid, &V, 1);//signal(mutex)if (strncmp(buffer, "end", 3) == 0) { sleep(1); break; }	//输入end,该生产者结束生产	}}//结束循环/*将共享内存从当前进程中分离*/int choice;printf("请选择1.退出 2.删除信号量和共享内存:");scanf("%d", &choice);if (shmdt(viraddr) == -1)exit(EXIT_FAILURE);//分离链接的共享内存if (choice == 2) {semctl(emptyid, IPC_RMID, 0);//撤销信号量集semctl(fullid, IPC_RMID, 0);semctl(mutexid, IPC_RMID, 0);if (shmctl(shmid, IPC_RMID, 0) == -1)exit(EXIT_FAILURE);//显示调用shmctl(带命令IPC_RMID)才能删除共享内存printf("已删除信号量和共享内存!\n");}else printf("已退出\n");exit(EXIT_SUCCESS);
}

2.消费者过程(Comsumer.c)

//Comsumer.c

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/shm.h>

#include <sys/ipc.h>
#include <errno.h>

#define BUF_SIZ 1024  //每个缓冲区的大小
#define N 10	//有N(例如N=10)个缓冲区

#define SEM_KEY_1 1
#define SEM_KEY_2 2
#define SEM_KEY_3 3
//#define SHM_KEY 4

union semun {//共同体类型semun
	int val;//使用的值
	struct semid_ds *buf;   //使用的缓存区,struct semid_ds信号量集的数据结构,在头文件 <sys/sem.h>
	unsigned short * array;//使用的数组
}arg;//该联合体没有定义在任何系统头文件中,因此得用户自己声明

int emptyid;//同步信号量(empty),限制生产者必须在缓冲区空时才生产
int fullid; //同步信号量(full),限制消费者必须在缓冲区有产品时才消费

int mutexid;//互斥信号量(mutex),限制生产者和消费者在访问缓冲区i时必须互斥

//共享内存区结构
struct shared_use_st {
	int Index[N]; //为0,表示对应的缓冲区i未被生产者使用,可生产但不可消费;为1,表示对应的缓冲区i已被生产者使用,不可分配但可消费
	char Buffer[N][BUF_SIZ];    //该缓冲队列有N个字符串缓冲区,每个缓冲区的大小为BUF_SIZ
};


int main() {
	/*****访问只有一个信号量的信号量集*****/
	emptyid = semget((key_t)SEM_KEY_1, 1, IPC_CREAT | 0666 | IPC_EXCL);//访问一个信号量集,第二个参数为信号量的个数
	//IPC_CREAT|IPC_EXCL 创建一个新的信号量集,如果该的信号量已经存在,则返回错误信息
	if (emptyid == -1) {//错误时返回-1
		if (errno == EEXIST) {//打开已有的信号量
			emptyid = semget((key_t)SEM_KEY_1, 1, IPC_CREAT | 0666);//打开已有的信号量
			fullid = semget((key_t)SEM_KEY_2, 1, IPC_CREAT | 0666);//只设置IPC_CREAT位,则创建一个信号量集,如果该信号量集已经存在,则返回其标识符
			mutexid = semget((key_t)SEM_KEY_3, 1, IPC_CREAT | 0666);
		}
	}/*定义PV操作*/struct sembuf P, V;
​	P.sem_num = 0;//信号量集的第0个元素
​	P.sem_op = -1;//<0相当于P操作,占有资源
​	P.sem_flg = SEM_UNDO;//指明内核为信号量操作保留恢复值
​	V.sem_num = 0;//信号量集的第0个元素
​	V.sem_op = 1;//>0相当于V操作,释放资源
​	V.sem_flg = SEM_UNDO;//指明内核为信号量操作保留恢复值/*****访问共享内存空间*****/int shmid;//共享内存标识符 
​	shmid = shmget((key_t)1121, sizeof(struct shared_use_st), 0666 | IPC_CREAT);//共享内存已存在,获得共享内存标识符/*将共享内存连接到当前进程的地址空间*/void * shm = NULL;//分配的共享内存的原始首地址
​	shm = shmat(shmid, (void*)0, 0); //返回共享内存的原始首地址if (shm == (void*)-1)exit(EXIT_FAILURE);//失败printf("Memory attached at %ld\n", (intptr_t)shm);struct shared_use_st * viraddr = NULL;//连接到该进程的虚地址空间,即应指向shm 
​	viraddr = (struct shared_use_st*)shm;//设置共享内存,viraddr连接到该进程的虚地址空间char buffer[BUF_SIZ];        //用于保存输入的文本int i = 0;while (1) {for (i = 0; i < N && viraddr->Index[i] == 0; i++);//找到可被消费者使用的缓冲区Buffer[i],Index为0表示缓冲区i被生产者占用if (i == N) {//当N个缓冲区都被生产者占用时输出“waiting...”sleep(1);        //sleep一段时间,再次进入循环printf("Waiting...\n");}else {semop(fullid, &P, 1);//最后一个参数值为1,只完成对一个信号量的操作  wait(full)semop(mutexid, &P, 1);//最后一个参数值为1,只完成对一个信号量的操作  wait(mutex)for (i = 0; i < N && viraddr->Index[i] == 0; i++);//找到可被消费者使用的缓冲区Buffer[i],Index为0表示缓冲区i被生产者占用//printf("Your message is:%s\n", viraddr->Buffer[i%N]);
​			viraddr->Index[i%N] = 0;//表示该缓冲区被消费者使用了semop(emptyid, &V, 1);//signal(empty)semop(mutexid, &V, 1);//signal(mutex)if (strncmp(viraddr->Buffer[i%N], "end", 3) == 0)break;//输入end,该消费者结束生产	}}//结束循环/*将共享内存从当前进程中分离*/int choice;printf("请选择1.退出 2.删除信号量和共享内存:");scanf("%d", &choice);if (shmdt(viraddr) == -1)exit(EXIT_FAILURE);//分离链接的共享内存if (choice == 2) {semctl(emptyid, IPC_RMID, 0);//撤销信号量集semctl(fullid, IPC_RMID, 0);semctl(mutexid, IPC_RMID, 0);if (shmctl(shmid, IPC_RMID, 0) == -1)exit(EXIT_FAILURE);//显示调用shmctl(带命令IPC_RMID)才能删除共享内存printf("已删除信号量和共享内存!\n");}else printf("已退出\n");exit(EXIT_SUCCESS);
}
三、 实验小结

计算机系统中,把并发进程的同步和互斥问题一般化,可以得到一个抽象的一般模型,即生产者-消费者问题。把系统中使用某一类资源的进程称为消费者,而把释放同类资源的进程称为该资源的生产者。

把一个长度为n的有界缓冲区(n>0)与一群生产者 P1,P2…Pm和一群消费者进程C1,C2…Cp联系起来。
在这里插入图片描述
信号量的设置:
Mutex=1 临界资源
Empty=n 空缓冲区的个数
Full=0 满缓冲区的个数,即可供消费的产品数量

进程通信中的共享存储区:
在存储器中划出了一块共享存储区,各生产者消费者进程可通过对共享存储区的读写来交换数据。
进程在通信前,先向系统申请获得共享存储区的描述符返回给申请者,然后,由申请者把获得的共享存储区连接到本进程上,以后就可像读、写普通存储器一样的读、写该共享存储区了。
共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。共享内存必须自己考虑同步问题,通常使用信号量同步或互斥访问共享存储。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用linux系统提供的信号量集和共享内存实现生产者和消费者问题 的相关文章

  • smb连接错误“请检查服务器名称或IP地址,然后再试一次,如果问题持续发生,请联系系统管理员“

    问题 公司内部的共享服务器突然访问不了了 xff08 iMac访问Windows共享 xff09 提示错误 请检查服务器名称或IP地址 xff0c 然后再试一次 xff0c 如果问题持续发生 xff0c 请联系系统管理员 经过试验 xff0
  • [Simple] 洗牌算法

    题目要求 xff1a 平时洗牌是两打牌 xff0c 交叉洗在一起 也就是开始 1 2 3 4 5 6 7 8 第一次 1 5 2 6 3 7 4 8 第二次 1 3 5 7 2 4 6 8 第k次 给你一个数组a 2N xff0c 要求在O
  • [转]Risc-V简要概括

    Risc V简要概括 1 Risc V硬件平台术语 一个RiscV硬件平台可以包含一个或多个RiscV兼容的核心 其它非RiscV兼容的核心 固定功能的加速器 各种物理存储器结构 I O设备以及允许这些部件相互连通的互联结构 比如下面的Si
  • 交叉编译ffmpeg-4.2.2

    最近想弄个USB camera xff0c 测试下视频功能 看到网上推荐使用FFmpeg这个开源工具 FFmpeg A complete cross platform solution to record convert and strea
  • 连接跟踪(conntrack)原理、应用以及Linux内核实现

    发现一篇好文章 xff0c 立即转载了来 xff0c 原地址 xff1a http arthurchiao art blog conntrack design and implementation zh 摘要 本文介绍连接跟踪 xff08
  • Java学习笔记(48)——生产者、消费者模型

    这里将自己学习java及其应用的一些笔记 积累分享一下 xff0c 如果涉及到了文章 文字侵权 xff0c 请联系我删除或调整 一 生产者 消费者模型 1 1 概述 生产者和消费者是线程间通信的一种模型 xff0c 这个问题是线程模型中的一
  • 【转】MEMS陀螺仪的简要介绍(性能参数和使用)

    MEMS传感器市场浪潮可以从最早的汽车电子到近些年来的消费电子 xff0c 和即将来到的物联网时代 如今单一的传感器已不能满足人们对功能 智能的需要 xff0c 像包括MEMS惯性传感器 MEMS环境传感器 MEMS光学传感器 甚至生物传感
  • 编译错误:multiple definition of `yylloc‘

    Using as source for kernel HOSTLD scripts dtc dtc usr bin ld scripts dtc dtc parser tab o bss 43 0x10 multiple definitio
  • 步进电机和伺服电机的区别

    步进电机作为一种开环控制的系统 xff0c 和现代数字控制技术有着本质的联系 在目前国内的数字控制系统中 xff0c 步进电机的应用十分广泛 随着全数字式交流伺服系统的出现 xff0c 交流伺服电机也越来越多地应用于数字控制系统中 为了适应
  • [网上摘录]Gerber RS274X-CAM文件格式详解

    GERBER是一种光绘文件格式 xff0c 用于描述光绘机进行各种绘制或运动行为 GERBER格式是EIA 标准RS 274D的子集 xff1b 扩展GERBER格式是EIA标准RS 274D格式的超集 xff0c 又叫RS 274X RS
  • 【转】开源如何盈利?

    开放源代码 xff08 Open Source xff09 运动曾经被视作商业的反义词 但现在已经有一些人在开源软件的基础上建立了一套完整的商业模式 xff0c 他们发现开源是一种能够与主流软件厂商有效进行竞争的系统性策略 开源软件在发展历
  • NandFlash详解

    转载自 xff1a https blog csdn net hellomxj1 article details 18984873 学习结构如目录所示 xff1a 一 NAND FLASH概述 二 NAND FLASH的参数及物理结构 三 N
  • Linux MTD架构下的nand flash驱动详解

    转载自 xff1a http blog csdn net wang zheng kai article details 18988521 有了前面的基础 xff08 Nandflash详解 xff1a https blog csdn net
  • 激光打印机工作原理(网上摘录)

    导读 xff1a 在本文中 xff0c 我们将揭开激光打印机背后的秘密 xff0c 追踪将计算机屏幕上的字符变为纸上的打印字母的神奇过程 当谜底揭开之后 xff0c 您便会明白 xff0c 其实激光打印过程基于一些很简单的科学原理 xff0
  • 女生适合学习Java吗?

    在这个信息爆炸的时代 xff0c 互联网行业成为了高薪的代名词 xff0c Java 技术因其具有卓越的通用性 高效性 平台移植性和安全性 xff0c 广泛应用于PC 数据中心 游戏控制台 科学超级计算机 移动电话和互联网 xff0c 作为
  • STM32 UART串口环形缓冲区的实现思路和代码

    同名微信公众号 固件工人 同步发布的文章 xff0c 欢迎同时关注 xff0c 及时获取最新文章 1 1 环形缓冲区的实现思路 单片机程序开发一般都会用到UART串口通信 xff0c 通过通信来实现上位机和单片机程序的数据交互 通信中为了实
  • 从零开始学习Linux(一)关闭虚拟机系统

    关闭系统 xff0c 需要输入如下命令 poweroff 然而 xff0c 你只能得到如下反馈 bash poweroff command not found 此项错误是因为poweroff命令是一个系统管理命令 执行此项命令需要高级使用者
  • AndroidQ SystemUI之power键灭屏锁屏流程

    本篇接着分析锁屏相关流程 xff0c 通常我们点击power键会灭屏 xff0c 灭屏时就会加载锁屏 xff0c 以便用户能在下次亮屏时第一时间看到锁屏 xff0c 我们就来看看点击power键灭屏锁屏的流程 Android的事件分发流程大
  • 用Word2007查找和替换功能批量设置图片位置

    Word2007的 查找和替换 功能并不仅仅可以对文字进行批量的查找替换 xff0c 还有很多神奇的功能 xff0c 比如对插入的 图片位置进行批量的查找和调整等等 今天我们就来试试Word2007的 查找和替换 功能在文字替换外的应用 x
  • Windows命令行删除文件和文件夹

    1 rd 命令帮助 删除文件夹 xff08 空 xff09 rd S 文件夹路径 2 rmdir Q S 目录 删除文件夹 xff08 非空 xff09 S 除目录本身外 xff0c 还将删除指定目录下的所有子目录 Q 安静模式 xff0c

随机推荐

  • 01 | 使用Gradle构建多模块项目

    系列文章目录 01 使用Gradle构建多模块项目 02 架构师必备 DDD领域驱动设计之落地实践 03 异常处理实践 抛异常 43 错误码 从今天开始 xff0c 我将从无到有的搭建一套基于 SpringBoot 的开发脚手架 xff0c
  • 干了三年java外包,我转AI了....

    谈及到程序员外包这件事 xff0c 我想我是比较有发言权的一个 xff0c 即使我现在已经从一个外包公司的JAVA开发转行做人工智能算法 我是2018年毕业的 xff0c 一毕业找的第一份工作就是一家外包公司 xff0c 主要做的是承接甲方
  • 人工智能的算法有哪些?AI常用算法

    人工智能 xff08 AI xff09 是一个非常广泛的领域 xff0c 其中包含许多不同的算法和技术 以下是一些常见的人工智能算法 xff1a 人工智能的算法有哪些 xff1f 机器学习 xff08 Machine Learning xf
  • Ftpsclient上传文件到ftp时storeFile总是返回false(522 data connections must be encrypted)

    使用java中org apache commons net ftp的FTPSClient将文件上传服务器时 xff0c 调用storeFile总是返回false 及时打印fClient getReplyCode 和fClient getRe
  • Android官方架构组件:Lifecycle详解&原理分析

    概述 在过去的谷歌IO大会上 xff0c Google官方向我们推出了 Android Architecture Components 其中谈到Android组件处理生命周期的问题 xff0c 向我们介绍了 Handling Lifecyc
  • 线程池原理——生产者/消费者

    import java util ArrayList import java util List import java util concurrent BlockingQueue import java util concurrent L
  • Java并发编程实战~生产者-消费者模式

    前面我们在 Worker Thread 模式 中讲到 xff0c Worker Thread 模式类比的是工厂里车间工人的工作模式 但其实在现实世界 xff0c 工厂里还有一种流水线的工作模式 xff0c 类比到编程领域 xff0c 就是生
  • REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案

    一 前言 在我们日常的开发中 xff0c 无不都是使用数据库来进行数据的存储 xff0c 由于一般的系统任务中通常不会存在高并发的情况 xff0c 所以这样看起来并没有什么问题 xff0c 可是一旦涉及大数据量的需求 xff0c 比如一些商
  • 一段日子的结束, 也是一段日子的开始

    一个朋友说的 xff0c 一段日子的结束 xff0c 也是另一段日子的开始 也正是我现在的状态 xff0c 我结束了一段往事 xff0c 也因此开始了一段日子 xff0c 曾经的曾经已离我远去 昨天和好朋友聊天到很晚 xff0c 谈了很多
  • 岁月静好

    不是说马年会马上转运的 xff0c 是不是蛇年的时候前半年太幸福了 xff0c 用了太多的好人品 xff0c 各种奖学金 xff0c 各种申请中标 xff0c 各种荣誉 xff0c 然后我要还了 小猴子说我开始会依赖人了 xff0c 哈哈
  • Android视图绑定ViewBinding的使用

    1 ViewBinding概述 使用ViewBinding的目的是轻松地编写可与视图交互的代码 将view和代码绑定在一起有多种方法 xff1a xff08 1 xff09 findViewById xff1a 编译不安全 xff0c 出现
  • Archlinux安装xfce4桌面

    Archlinux安装xfce4桌面 安装桌面环境 pacman S xorg 安装xfce4桌面 pacman S xfce4 安装LightDM显示管理器 pacman S lightdm lightdm gtk greeter Lig
  • 线程池 allowCoreThreadTimeOut 小知识

    关于线程池 xff1a allowCoreThreadTimeOut true 的小知识点 1 背景 检查别人程序发现堆内存2G xff0c 但是常驻内存res到3 2G了 xff0c 经过一系列操作发现线程特别多 1000 43 当然有重
  • 解决用Hexo和GitHub搭建博客时hexo d命令报错问题

    这两天学着使用hexo和github page搭建个人博客 到使用hexo deploy自动部署到github page的时候出现了错误 xff1a FATAL bash dev tty No such device span class
  • 书房再次升级啦~~

    国庆长假 xff0c 在家里面一顿折腾 xff0c 墙全部重新粉刷 xff0c 书房 卧室 客厅三种不同颜色 书房的颜色是当时在装饰城的展厅里面偷偷扣的墙皮 xff0c 在多乐士店色卡里面对出来的 xff0c 哈哈 ps 这篇日志的照片是用
  • android 抓取LOG的几种命令

    通常调试时候需要抓取log信息 xff0c 下面几种通过ADB命令来抓取log的方法 xff1a USB连接上手机 xff0c 手机需要其他操作 xff1b 然后运行ADB工具 xff1b 输入不同的命令即可抓取对应的LOG信息 抓取rad
  • 基于K近邻法的手写数字图像识别

    数字图像处理课程论文 题目 xff1a 数字图像识别 摘要 模式识别 PatternRecognition 是一项借助计算机 xff0c 就人类对外部世界某一特定环境中的客体 过程和现象的识别功能 xff08 包括视觉 听觉 触觉 判断等
  • Android apk图片资源目录存放规则(drawable和mipmap的区别)

    mipmap mdpi 48 48 mipmap hdpi 72 72 mipmap xhdpi 96 96 mipmap xxhdpi 144 144 mipmap xxxhdpi 192 192 drawable ldpi xff1a
  • 动态代理实现AOP

    阅读目录 代理静态代理动态代理动态代理的应用场景AOPAOP实例1AOP实例2 回到顶部 代理 代理顾名思义 xff1a 代为处理 不是对目标对象的直接操作 xff0c 而是通过代理对目标对象进行包装 xff0c 此时可以在目标对象的基础上
  • 使用linux系统提供的信号量集和共享内存实现生产者和消费者问题

    使用linux系统提供的信号量集和共享内存实现生产者和消费者问题 实验目的 了解和熟悉linux系统下的信号量集和共享内存 实验任务 使用linux系统提供的信号量集和共享内存实现生产者和消费者问题 实验要求 1 写两个程序 xff0c 一