Linux系统编程(三)进程间通信(IPC)

本文目录

  • 一、linux 进程之间的通信种类
  • 二、管道
    • 1. 管道的概述
    • 2. 什么是管道文件?
    • 3. 管道的特点
    • 4. 管道类型
      • (1)无名管道(pipe)
      • (2)有名(命名)管道(fifo)
  • 三、信号(signals)
    • 1. 信号发送相关函数
    • 2. 信号接收处理
  • 四、消息队列
    • 1. Linux中的消息队列有两种类型
    • 2. 消息队列与有名管道(FIFO)的异同点
    • 3. System V IPC 机制消息队列相关函数
    • 4. 使用例程
  • 五、共享内存
    • 1. 共享内存原理
    • 2. 共享内存特点
    • 3. 相关函数
    • 4. 使用例程

  

一、linux 进程之间的通信种类

序号通信方式描述
1管道(无名管道、有名管道)无名管道允许亲缘关系进程间的通信。有名命名管道还允许无亲缘关系进程间通信。
2信号 signal在软件层模拟中断机制,通知进程某事发生。它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
3消息队列 Messge Queue是消息的链接表,包括 posix 消息队列和 SystemV 消息队列。它克服了前两种通信方式中信息量有限的缺点。
4共享内存 Shared memory可以说是最有用的进程间通信方式,是最快的可用 ipc 形式。是针对其他通信机制运行效率较低而设计。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
5信号量Semaphore进程间同步。主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
6套接字 socket用于网络中不同机器间进程通信。

二、管道

1. 管道的概述

   管道好比一条水管,有两个端口,一端进水,另一端出水。 管道是 Linux 进程间通信的一种方式,如管道命令 ls -l | grep anaconda3,意思是从ls -l中搜索含有anaconda3的文件内容。

2. 什么是管道文件?

   我们软件的管道文件也有两个端口,分别是读端和写端。进水可看成数据从写端被写入,出水可看数据从读端被读出。

3. 管道的特点

(1)管道通信是单向的,有固定的读端和写端;
(2)数据被进程在管道读出后,管道中的数据就不存在了;
(3)当进程去读取空管道的时候,进程会阻塞;
(4)当进程往满管道写入数据时,进程会阻塞;
(5)管道容量为 64KB;

4. 管道类型

   管道类型分为无名管道命名(有名)管道两类。无论是哪种管道我们都用 read、 write 函数来对管道进行读写。对于不同的管道类型有不同的方法。
   对于无名管道,由于读端和写端处于血缘关系的进程中(同一个main函数中),所以必须要知道读写两端分别对应的文件描述符。
   而对于命名(有名)管道,读端和写端处于毫无关系的两个进程中,所以需要创建一个文件作为管道,来对文件进行读写操作。需要注意:管道文件不支持创建在共享目录下,因为共享目录也属于windows。

(1)无名管道(pipe)

   无名管道用于在一个main里的进程中,必须是父子进程或兄弟进程(一个父进程创建的多个子进程之间的关系)。
   例如创建无名管道时,我们使用pipe()来创建无名管道。对于无名管道的读写文件描述符我们通常保存在一个有两个整型元素的数组中,如 int fds[2]。然后调用函数 pipe(fds),这个函数会创建一个管道,并且数组 fds 中的两个元素会成为管道读端和写端对应的两个文件描述符。即 fds[0]为读端文件描述符, fds[1]为写端文件描述符。

无名管道的特点:
① 只能在亲缘关系进程间通信(父子或兄弟)。
② 半双工(固定的读端和固定的写端)。
③ 它是特殊的文件,可以用 read、write 等函数操作,这种文件只能在内存中。

   管道两端的关闭是有先后顺序的。如果先关闭写端则从另一端读数据时,read 函数将返回 0,表示管道已经关闭;但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到 SIGPIPE 信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的 write 函数返回一个负值,表示管道已经关闭。

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

//                                进程间通信


int main(int argc,char **argv)
{
     int rec;
	 int pipefd[2];             //pipefd[0]读    pipefd[1]写
     rec= pipe(pipefd);
	 
	 if(rec < 0)
 	 {
       printf("error!\n");
     }
	 
	 pid_t pid;
	 pid=fork();                       //创建进程
	 int i=0;
	 if(pid==0)
	 	{                       //子进程写
	      char buff[64];
		   while(1)
		  	{ 
		  	  i++;
			  memset(buff,0,strlen(buff));             //清空数组
			  fgets(buff, sizeof(buff),stdin);         //从终端获取字符给buff
			  write(pipefd[1], buff, sizeof(buff));    //将buff数组写入管道
			  if(i==3) break;
	        }
		   close(pipefd[1]);                        //关闭管道
		   close(pipefd[0]);
		   exit(0);                                //退出子进程 
	    }
	 
	 else if(pid>0)
	 	{                   //父进程读
	 
	      char data[64];
		  int n;
		  wait(&n);                               //等待子进程结束
		  while(1)
		 	 {    i++;
				  memset(data,0,strlen(data));
				  read(pipefd[0], data, sizeof(data));    //从管道中读取内容传给data数组 
				  printf("%s",data);
				  if(i==3) break;
		 	  }
				  close(pipefd[1]);
				  close(pipefd[0]);
				  exit(0);      
		 }
	 else 
	 	{
         printf("error!\n");
	    }
}

(2)有名(命名)管道(fifo)

   无名管道只能在亲缘关系的进程间通信,这大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的形式实现不相关进程间的通信。
   这里我们使用两个不相关的进程分别来进行通信,即使用两个命令窗口分别运行管道读端的程序和写端的程序。首先我们需要在写端使用命令mkfifo创建管道。其第一个参数为创建的管道文件名,第二个参数为文件的权限。

问题:既然是创建文件作为管道,那么这个管道文件和普通文件有什么区别呢?
   答:普通文件:用于存储数据,数据可以随机访问,可以读写多次,数据在文件关闭后依然存在。命名管道:用于进程间通信,数据是流式的,主要用于一次性读写,数据在被读取后即被移除,不持久存储。数据以流的形式传输,写入的数据只能按顺序读取,通常一个进程写入数据后,另一个进程立即读取。

●有名管道写端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

//   有名管道:用于两个无亲属关系的进程间的通信。 例如fifo_w 和fifo_r 间的通信。
int main(int argc, char **argv)
{
   int fd; 
   char buff[64];    
   
   //创建一个有名管道文件,文件权限为可读可写
   mkfifo("/tmp/fifo.cmd",0666); 
    
   //系统io来打开文件  ,不是标准io。打开有名管道的写端。
   fd=open("/tmp/fifo.cmd",O_WRONLY);
   
   if(fd < 0) printf("error!\n");
   
  while(1)
  	{  
       memset(buff, 0, sizeof(buff));
       fgets(buff, sizeof(buff), stdin);         //从终端获取字符给buff
       write(fd, buff, strlen(buff));
   }
   
   close(fd);
  
	//删除有名管道文件
   // unlink("/tmp/fifo.cmd");  

   return 0;
}

●有名管道读端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

//管道文件不支持创建在共享目录下,因为共享目录也属于windows
//有名管道:无亲属关系的进程间通信(不在同一个main函数中的进程)
//  fopen:标准io           open:系统io

int main(int argc, char **argv)
{
   int fd; 
   char buff[64];
   
   //打开有名管道的读端(因为写端已经创建了管道文件,所以读端只需要打开就行)
   fd = open("/tmp/fifo.cmd", O_RDONLY);   
   
   if(fd < 0) printf("error!\n");
   
   while(1)
  	{  
       memset(buff,0,sizeof(buff));
       read(fd, buff, sizeof(buff));    //read 必须用 sizeof!!
	   printf("%s",buff);
   }
   
   close(fd);
	
	//删除有名管道文件
	// unlink("/tmp/fifo.cmd");	
}

三、信号(signals)

   在Linux中,信号是一种进程间通信机制,用于通知进程某些事件的发生。信号是一种异步的通知机制,当一个进程接收到信号时,可以选择处理该信号、忽略它或执行默认的操作。信号在系统编程中非常重要,常用于控制进程行为、处理异常情况和执行进程间通信。

信号类型如下:
   对信号进行处理时,使用信号名称和使用信号编号效果相同。在这里插入图片描述

1. 信号发送相关函数

(1)向指定进程发送信号

   在函数中可以使用下面代码,在命令行中可以使用kill -信号编号 进程PID号,来向指定的进程发送指定的信号。我们可以在命令行使用ps -ef来查看当前所有进程的详细信息。

int kill(pid_t pid, int sig); 
/*当 pid>0 将信号发送给指定进程;
当 pid==0 时,将信号发送给同组进程;
当 pid<0 时,将信号发送给进程组 ID 等于 pid 绝对值的进程;
当 pid==-1 时,将信号发送给所有进程;

int sig:信号指令(类型),如 SIGQUIT
*/

(2) 向进程自己发送信号

int raise(int sig);

(3)挂起调用该函数的进程,直到捕获到了一个信号。

int pause(void);

2. 信号接收处理

函数用于设置一个信号处理函数,当特定信号发生时,该函数会被调用。

sighandler_t signal(int signum, sighandler_t handler);
//int signum:要捕获或处理的信号编号。
//sighandler_t handler :接收到指定信号后要执行的函数。

使用例程:当按下Ctrl+c时会触发自定义函数,打印出signal:2。可以使用Ctrl+z结束该进程。

#include <stdio.h>
#include <signal.h>

void fun(int arg)    //自定义函数
{ 
   printf("signal:%d\n",arg);
}

int main(int argc,char **argv)
{
    //当进程收到2号信号时,执行自定义的处理函数
	signal(2, fun);
	while(1)
	{
     
	}
}

四、消息队列

1. Linux中的消息队列有两种类型

System V消息队列(传统消息队列)
POSIX消息队列(现代消息队列)

2. 消息队列与有名管道(FIFO)的异同点

(1)相同点:
   消息队列与 FIFO 很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。
(2)不同点:
   FIFO 需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。

3. System V IPC 机制消息队列相关函数

(0)包含的头文件

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

(1)定义信息包结构体

//数据结构体
typedef struct{
    char buff[10];
	int number;
	float sorce;
} my_data;

//信息包结构体
struct msgbuf{
    long mtype;
	my_data data;
};

(2)创建密钥-关键字
   用于生成一个唯一的键(key),通常用于创建System V IPC对象,如共享内存、信号量和消息队列。 成功的时候,返回密钥值。失败返回-1。

key_t ftok(const char *pathname, int proj_id);
//const char *pathname :文件的路径全称且文件必须存在。
// int proj_id :非0的唯一识别键值id,不要重复。

(3)创建和访问一个消息队列
   成功的时候,返回一个消息队列的唯一标识符 id(跟进程 ID 是一个类型)。失败返回-1。

int msgget(key_t key, int msgflg);
//key_t key:上一步生成的密钥。
//msgflg:指明队列的访问权限和创建标志。创建标志的可选值为 IPC_CREAT 和 IPC_EXCL。队列权限自定义。

(3)将消息添加到消息队列中

int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsz,  int msgflg);
//int msqid :步骤3返回的消息队列的id。
//struct msgbuf * msgp :发送信息的结构体
//size_t msgsz :消息包的大小
//int msgflg:可以为 0(通常为 0)或 IPC_NOWAIT。

(4)从消息队列中读取消息

ssize_t msgrcv(int msqid, struct msgbuf * msgp, size_t msgsz, long msgtyp, int msgflg);
//int msqid :步骤3返回的消息队列的id。
//struct msgbuf * msgp :发送信息的结构体
//size_t msgsz :消息包的大小
// long msgtyp :要接收的消息类型。如果指定为0,则接收队列中的第一条消息。如果大于0,则接收队列中第一个类型字段等于 msgtyp 的消息。如果小于0,则接收队列中第一个类型字段小于或等于 msgtyp 的消息。通常,msgtyp 是一个正整数,用于区分不同类型的消息。
//int msgflg:可以为 0(通常为 0)或 IPC_NOWAIT。

(4)删除消息队列

int msgctl(int msqid, int cmd, struct msqid_ds * buf);
/*
	msqid 是由 msgget 返回的消息队列标识符。
	cmd 通常为 IPC_RMID 表示删除消息队列。
	buf 通常为 NULL 即可。
*/

4. 使用例程

●消息队列写端:每执行一次该程序就把消息发送出去一次,一直累加到消息队列中,等待读取。

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/msg.h>

//用命令:ipcs 查看消息队列内容

int main(int argc, char ** argv)
{
	key_t  key;
	int  ret;
	struct msgbuf mbuf;
	int msgid;
	//创建密匙(文件的路径全称,文件必须存在)
	key=ftok("/mnt/hgfs/Share/7.进程间通信/5.消息队列/key.txt", 1); 
	if(key <0){
	   perror("error\n");
	   return -1;
	}
	
	//创建或访问消息队列
	//消息队列的id,密钥,不存在时创建|读写权限
	msgid = msgget(key,IPC_CREAT | 0666);
	if(msgid <0){
	   perror("msgget error\n");
	return -1;
	}
	
	//消息的类型,用于区分消息包的。
	mbuf.mtype=1;     
	//填充消息体
	mbuf.data.number=121;
	mbuf.data.sorce=12.31;
	strcpy(mbuf.data.buff,"i love you!");

	//消息队列的id,待发送消息地址,消息体的大小,0
	ret = msgsnd(msgid, &mbuf, sizeof(struct msgbuf), 0); 
	if(ret < 0){
	   perror("msgsnd error\n");
	   return -1;
	}
	//删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

●消息队列读端:每执行一次,就从消息队列中读取一次数据,消息队列中消息-1,直至读完。

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <sys/msg.h>

//用命令:ipcs 查看消息队列内容

int main(int argc, char ** argv)
{
	key_t  key;
	int  ret;
	struct msgbuf mbuf;
	int msgid;
	//创建密匙(文件的路径全称,文件必须存在)
	key=ftok("/mnt/hgfs/Share/7.进程间通信/5.消息队列/key.txt", 1); 
	if(key <0){
	   perror("error\n");
	   return -1;
	}
	
	//创建或访问消息队列
	//消息队列的id,密钥,不存在时创建|读写权限
	msgid = msgget(key,IPC_CREAT | 0666);
	if(msgid <0){
	   perror("msgget error\n");
	   return -1;
	}
	
	//消息队列的id,读取消息的缓冲区,消息体的大小,读到第一个消息,0
	ret=msgrcv(msgid,&mbuf, sizeof(struct msgbuf), 0 , 0 );
	if(ret < 0){
		perror("msgsnd error\n");
		return -1;
	}
	
	printf("%d\n",mbuf.data.number);
	printf("%f\n",mbuf.data.score);
	printf("%s\n",mbuf.data.buff);
	
	//删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

五、共享内存

   共享内存也是进程间(进程间不需要有继承关系)通信的一种常用手段。一般 OS 通过内存映射与页交换技术,使进程的内存空间映射到不同的物理内存,这样能保证每个进程运行的独立性,不至于受其它进程的影响。但可以通过共享内存的方式,使不同进程的虚拟内存映射到同一块物理内存,一个进程往这块物理内存中更新的数据,另外的进程可以立即看到这块物理内存中修改的内容。多个进程可以直接读写共享的内存区域,不需要进行数据的复制或者传递。

1. 共享内存原理

(1)进程间需要共享的数据被放在该共享内存区域中。
(2)所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。
(3)这样一个使用共享内存的进程可以将信息写入该空间,而另一个使用共享内存的进程又可以通过简单的内存读操作获取刚才写入的信息,使得两个不同进程之间进行了一次信息交换,从而实现进程间的通信。
(4)共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要在所有进程的虚拟内存都有相同的地址。
(5)进程对象对于共享内存的访问通过 key(键)来控制,同时通过 key 进行访问权限的检查。

2. 共享内存特点

   共享内存是最快的一种通信方式,适合大量数据的传输。只要创建的密钥一样,就可以共享内存空间。如果没有亲缘关系的进程使用共享文件,则需要密钥。如果有亲缘关系的进程使用共享文件,把key改为IPC_PRIVATE

3. 相关函数

(0)包含的头文件

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

(1)定义信息包结构体

//数据结构体
typedef struct{
    char buff[10];
	int number;
	float sorce;
} my_data;

//信息包结构体
struct msgbuf{
    long mtype;
	my_data data;
};

(2)创建密钥-关键字
   用于生成一个唯一的键(key),通常用于创建System V IPC对象,如共享内存、信号量和消息队列。 成功的时候,返回密钥值。失败返回-1。

key_t ftok(const char *pathname, int proj_id);
//const char *pathname :文件的路径全称且文件必须存在。
// int proj_id :非0的唯一识别键值id,不要重复。

(3)创建/打开共享内存

成功则返回一个该共享内存段的唯一标识号(唯一的标识了这个共享内存段)。否则返回-1。

int shmget(key_t key, int size, int shmflg);
/*key :是一个与共享内存段相关联的关键字。
Size:指定共享内存段的大小,以字节为单位。
Shmflg:是一掩码合成值,可以是访问权限值与(IPC_CREAT 或 IPC_EXCL)的合成。 
        IPC_CREAT 表示如果不存在该内存段,则创建它。
        IPC_EXCL 表示如果该内存段存在,则函数返回失败结果(-1)。
 */

(4)映射到进程空间地址

如果调用成功,返回映射后的进程空间的首地址,否则返回(void*)-1。

void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
	Shmid:共享内存段的标识 通常应该是 shmget 的成功返回值。
	Shmaddr:共享内存连接到当前进程中的地址位置。通常是 NULL,表示让系统来选择共享内存出现的地址。
	Shmflg:一组位标识,通常为 0 即可。
*/

(5)共享内存段与进程空间分离

将共享内存分离并没删除它,只是使得该共享内存对当前进程不再可用。 成功返回 0,失败时返回-1

int shmdt(const void *shmaddr);
//shmaddr 为 shmat 的成功返回值。

(6)删除共享内存段

成功返回 0,失败时返回-1

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
Shmid:共享内存段标识 通常应该是 shmget 的成功返回值。
Cmd:对共享内存段的操作方式。
      可选为 IPC_STAT,IPC_SET,IPC_RMID。
     通常为 IPC_RMID,表示删除共享内存段。
Buf:共享内存段的信息结构体数据,通常为 NULL。
*/

4. 使用例程

●写入共享内存

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

#define SHM_MAX 1024

int main(int argc, char **argv)
{
     key_t  key;
	 int shmid;
	 char *p;
	 my_data *data;
      //创建密匙(文件的路径全称,文件必须存在)
	 key=ftok("/home/qjl/1.sh", 0); 
	 if(key <0){
         perror("error\n");
		 return -1;
	  }
	 
	 //所创建/打开的共享内存的id,(密匙,空间大小,打开的空间不存在时创建)
	 shmid=shmget(key, SHM_MAX, IPC_CREAT);
     if(shmid<0) {
         perror("error\n");
		 return -1;
	  }
	 
     //将共享内存映射到本进程的空间中
     //映射后的起止地址,待操作的空间,映射起止地址,0
	 data=(my_data *)shmat(shmid, NULL, 0);
	 if(data ==(my_data *)-1){
         perror("error\n");
		 return -1;
	  }

//方式一:
                  //往共享的地址空间写东西
	              //结构体指针用->访问
/*	 data->n=10;          
	 data->f=12.33;
	 strcpy(data->buff,"hello");
*/
//方式二:(安全,防止数据混乱)
	  my_data d;
      d.n=222;
      d.f=123.1;
	  strcpy(d.buff,"world");
	  memcpy(data,&d,sizeof(my_data));  //将d的数据拷贝到data中

     //将共享内存空间从本进程中分离
	 shmdt(data);
	 
	 //删除共享内存空间
    // shmctl(shmid,IPC_RMID,NULL);
    
	 return 0;
	
}

●读取共享内存

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

#define SHM_MAX 1024

int main(int argc, char **argv)
{
     key_t  key;
	 int shmid;
	 char *p;
	 my_data *data;
	 my_data  d;
	 
	 char buff[64]={0};
	 
      //创建密匙(文件的路径全称,文件必须存在)
	 key=ftok("/home/qjl/1.sh", 0); 
	 if(key <0){
         perror("error\n");
		 return -1;
	  }
	 
	 //所创建/打开的共享内存的id,(密匙,空间大小,打开的空间不存在时创建)
	 shmid=shmget(key,SHM_MAX,IPC_CREAT);
     if(shmid<0) {
         perror("error\n");
		 return -1;
	  }
	 
     //将共享内存映射到本进程的空间中
     //映射后的起止地址,待操作的空间,映射起止地址,0
	 data=(my_data *)shmat(shmid, NULL, 0);
	 if(data== (my_data *)-1){
         perror("error\n");
		 return -1;
	  }
	 //从共享空间读取
	 memcpy(&d, data, sizeof(my_data));  //将共享空间data的数据读到d中
	 
	 printf("%d\n",d.n);
	 printf("%f\n",d.f);
	 printf("%s\n",d.buff);

     //将共享内存空间从本进程中分离
	 shmdt(data);
	 
	 //删除共享内存空间
    // shmctl(shmid,IPC_RMID,NULL);
    
	 return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JVM】内存区域划分 | 类加载的过程 | 双亲委派机制 | 垃圾回收机制

文章目录 JVM一、内存区域划分1.方法区&#xff08;1.7之前&#xff09;/ 元数据区&#xff08;1.8开始&#xff09;2.堆3.栈4.程序计数器常见面试题&#xff1a; 二、类加载的过程1.类加载的基本流程1.加载2.验证3.准备4.解析5.初始化 2.双亲委派模型类加载器找.class文件的过…

wetool企业版使用教程及下载方式 微兔该如何使用 wetool还能用吗 wetool扳手工具wetool操作方法难吗 wetool有哪些功能

今天给大家推荐一款我们目前在使用的电脑群发工具掘金小蜜&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 掘金小蜜&#xff08;只支持Win7及以上操作系统&#xff0c;没有推Mac版和手机客户…

晶圆厂的PE转客户工程师前景怎么样?

知识星球&#xff08;星球名&#xff1a; 芯片制造与封测技术社区&#xff0c;星球号&#xff1a; 63559049&#xff09;里的学员问&#xff1a; 目前在晶圆厂做PE&#xff0c;倒班oncall压力太大把身体搞坏了&#xff0c;现在有一个design house的CE客户工程师的offer&…

【class15】人工智能初步----语音识别(2)

【class15】 本节课&#xff0c;我们将学习以下三个知识点&#xff1a;1. wav文件2. 从视频中获取音频文件3. 对音频文件进行参数设置接下来&#xff0c;我们一起学习吧&#xff5e; 声音是一种波&#xff0c;电脑只能对采样后所得的数字进行处理。常见的音频格式有很多&…

UCOSII_STM32F1移植详细过程(一)

UCOSII_STM32F1移植详细过程&#xff08;一&#xff09; 1、概述2、关于C/OS3、移植过程&#xff08;文件描述与提取&#xff09;1.软件工程文件夹描述2.提取工程中有用的文件3.提取ST标准外设库有用的文件2.新建、修改文件 1、概述 该文写针对初学C/OS的朋友&#xff0c;基于…

数据集001:安全帽检测数据集 (Helmet Detection) (含数据集下载链接)

安全帽检测 安全帽识别是一个目标检测任务&#xff0c;及时排查安全帽佩戴的规范性并给予提醒&#xff0c;可以大大降低施工安全隐患。这是CV领域入门级的项目&#xff0c;能快速了解从数据预处理、模型构建、训练到部署的整体流程。 数据集格式 数据集中包含了5000张已经标注…

从垃圾识别到收集器:详细聊聊Java的GC

个人博客 从垃圾识别到收集器:详细聊聊Java的GC | iwts’s blog 前言 聊GC&#xff0c;自然离不开JVM内存模型&#xff0c;建议先了解JVM内存模型相关内容&#xff0c;或者最起码了解堆相关的内容&#xff0c;GC主要处理的就是堆。 这里会从垃圾识别算法->GC算法->JV…

工具使用-网络性能测试工具(iperf)-TCP 和 UDP 的吞吐量-包转发率参数的理解

时间戳&#xff1a;2024年5月26日15:18:39 iperf 和 netperf 都是最常用的网络性能测试工具&#xff0c;测试 TCP 和 UDP 的吞吐量。它们都以客户端和服务器通信的方式&#xff0c;测试一段时间内的平均吞吐量。 接下来&#xff0c;我们就以 iperf 为例&#xff0c;看一下 TC…

Linux防火墙之iptables

一. iptables防火墙的相关知识 1.1 防火墙的概念 防火墙&#xff08;英语&#xff1a;Firewall&#xff09;技术是通过有机结合各类用于安全管理与筛选的软件和硬件设备&#xff0c;帮助计算机网络于其内、外网之间构建一道相对隔绝的保护屏障&#xff0c;以保护用户资料与信…

复习java5.26

面向对象和面向过程 面向过程&#xff1a;把一个任务分成一个个的步骤&#xff0c;当要执行这个任务的时候&#xff0c;只需要依次调用就行了 面向对象&#xff1a;把构成任务的事件构成一个个的对象&#xff0c;分别设计这些对象&#xff08;属性和方法&#xff09;、然后把…

【数据结构课程学习】二叉树_堆:Lesson2

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 1.二插树的概念和结构 &#x1f697;二叉树的概念&#xff1a; &#x1f697;特殊的二叉树&am…

JVM学习-Class文件结构①

字节码文件的跨平台性 Java语言&#xff1a;跨平台的语言(Write Once,Run Anywhere) 当Java源代码编译成字节码后&#xff0c;如果想在不同平台上运行&#xff0c;则无须再次编译这上优势不再那么吸引人&#xff0c;Python,PHP,Ruby,Lisp等有强大的解释器跨平台似乎已经成为一…

【iOS开发】—— KVC

【iOS开发】—— KVC 一. KVC的定义key和keyPath的区别用法&#xff1a; 批量复制操作字典模型相互转化KVC的其他方法 KVC原理赋值原理取值原理 一. KVC的定义 KVC&#xff08;Key-value coding&#xff09;键值编码&#xff0c;就是指iOS的开发中&#xff0c;可以允许开发者通…

C#--SVG矢量图画法示例

1.代码示例 <Viewbox Grid.Column"1" Grid.ColumnSpan"1" Grid.RowSpan"1" ><Path Name"ValveShape" Stroke"Black" Data"M 50,0 L 150,200 L 50,200 L 150,0 Z" Width"200" Height"…

文件系统--inode

文章目录 概述认识磁盘了解磁盘的存储结构对磁盘的存储结构进行逻辑抽象 操作系统对磁盘的使用宏观认识细节认识再谈目录再谈文件的增删 概述 文件有很多&#xff0c;但是被打开的文件很少&#xff0c;这些没有被打开的文件在磁盘中&#xff0c;这就叫做磁盘文件。每次先打开一…

【JavaEE初阶】网络初识|局域网和广域网|交换机和路由器|IP地址|端口号

&#x1f4a1;推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳转到网站】 关键概念 1.局域网LAN和广域网WAN &#xff08;1&#xff09;局域⽹&#xff0c;即Local Area Network&#xff0…

一文扫尽Nas常用Docker软件

NAS&#xff08;Network Attached Storage&#xff0c;网络附加存储&#xff09;设备上的Docker软件选择取决于您的具体需求和用途。以下是一些NAS上常用的Docker软件推荐&#xff1a; Docker管理工具&#xff1a; Watchtower&#xff1a;它可以自动更新Docker容器中的镜像&…

浮点型比较大小

浮点数的存储形式 浮点数按照在内存中所占字节数和数值范围&#xff0c;可以分为浮点型&#xff0c;双精度浮点型和长双浮点型数。 代码&#xff1a; printf("lgn:%e \n", pow(exp(1), 100));printf("lgn:%f ", pow(exp(1), 100));输出结果&#xff1a; …

Vue | 自定义组件双向绑定基础用法

Vue | 自定义组件双向绑定基础用法 vue 中&#xff0c;由于单向数据流&#xff0c;常规的父子组件属性更新&#xff0c;需要 在父组件绑定相应属性&#xff0c;再绑定相应事件&#xff0c;事件里去做更新的操作&#xff0c;利用语法糖 可以减少绑定事件的操作。 这里就简单的梳…

ROS | 激光雷达包格式

ros激光雷达包格式&#xff1a; C实现获取雷达数据 &#xff1a; C语言获取雷达数据&#xff1a; Python语言获取雷达数据&#xff1a; python不需要编译&#xff0c;但是需要给它一些权限 chmod x lidar_node.py(当前的文件名字&#xff09; C实现雷达避障&#xff1a; python…