嵌入式开发学习 IPC Day13-15(2024.03.31-04.3)

本文详细介绍了Linux系统中的进程间通信方式,如管道(无名管道和有名管道)、信号、消息队列、共享内存、信号量以及线程的相关概念和使用示例,包括多路复用IO模型和线程的创建、退出、同步等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程间通讯

通讯方式:管道、信号、消息队列、共享内存、网络

管道

管道分类:无名管道(父子进程之间通讯),有名管道(任意进程之间通讯)
管道本质:在内存建立一段缓冲区,由操作系统内核来负责创建于管理
在这里插入图片描述

无名管道

特点

单向通讯,只能用于父子进程,发送端和接受端称为写端和读端,无名管道抽读端和写端抽象为两个文件进行操作,无名管道创建成功后会返回读端和写端的文件描述符。当管道为空时,读进程会阻塞

函数

#include <unistd.h>
/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES /
struct fd_pair {
long fd[2];
};
struct fd_pair pipe();
/
On all other architectures */
int pipe(int pipefd[2]); //pipefd[0] 读端文件描述符 pipefd[1]写端描述符 成功返回1 失败返回0

**eg:**创建子进程,并向子进程发送消息(先创建无名管道,后创建子进程)

有名管道

特点

有名管道在文件系统中是可见的文件,但是不占用磁盘空间,任然在内存中(占用内存空间),可以通过 mkfifo 命令创建有名管道.
有名管道用于在任意进程之间通讯,当管道为空时,读进程会阻塞
在这里插入图片描述

mkfifo函数(Linux 同名函数)

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

示例

信号

在这里插入图片描述
在这里插入图片描述
kill -l 可以查看系统信号
在这里插入图片描述

在这里插入图片描述

相关函数

raise
kill
pause

自定义处理函数

在这里插入图片描述

signal函数

在这里插入图片描述

strsingal
alarm函数(定时器信号)

在这里插入图片描述

子进程退出(自定义信号处理)

在这里插入图片描述

消息队列

IPC:Inter-Process Communicate
IPC对象:消息队列、共享内存、信号量
IPC是由内核维护的若干个对象,通过ipcs命令查询
每个ipc对象都有唯一的id号,通过ftok函数生成
消息队列(内核维护)支持任意两个进程之间(多个进程可以使用)通讯,具有IFIO的特性
在这里插入图片描述
在这里插入图片描述

创建消息队列 msgget

在这里插入图片描述

控制消息队列 msgctl

消息队列的发送与接收 msgsnd,magrcv

共享内存

在这里插入图片描述

创建共享内 shmget

在这里插入图片描述

控制共享内存 shmctl

共享内存映射 shmat

共享内存映射解除 shmdt memcpy(拷贝共享内存至数组)

信号量机制实现进程同步

解决不同进程对临界资源的竞争问题。
临界资源:同一时刻只能被一个进程使用的设备
临界区:使用临界资源的代码段
互斥:同一时刻只有一个进程访问临界资源
同步:在互斥的基础上使临界资源有序访问
Linux中查询信号量的使用 ipcs -s
eg:不使用信号量,父子进程同时向stdout输出

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

//  pid_t fork(void);
int main(void){
	pid_t cpid;
	cpid = fork();
	if(cpid == -1){
	
		perror("[ERROR] fork():");
		exit(EXIT_FAILURE);
	}
	if(cpid == 0 ){//子进程
		for(;;){
			printf("----------------------------------\n");
			printf("C start.\n");
			sleep(1);
			printf("C end. \n");
			printf("----------------------------------\n");
		}
	}
	else if(cpid > 0){//父进程
		for(;;){
			printf("----------------------------------\n");
			printf("F start.\n");
			sleep(1);
			printf("F end. \n");
			printf("----------------------------------\n");
		}
		wait(NULL);
	
	}

	return 0;
}

结果

root@wangjudealy:/learn# ./a.out 
----------------------------------
F start.
----------------------------------
C start.
F end. 
----------------------------------
----------------------------------
F start.
C end. 
----------------------------------
----------------------------------
C start.
F end. 
----------------------------------
----------------------------------
F start.
C end. 
----------------------------------
----------------------------------
C start.
^C

创建信号量集合

一个信号量集合包含多个信号量,从0开始编号

semget

在这里插入图片描述
创建
代码

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

#define SEM_SET_PATHNAME "."
#define SEM_SET_PRO_ID 103
//int semget(key_t key, int nsems, int semflg);
//  pid_t fork(void);
int main(void){
	//创建信号量集合
	//1、创建key
	key_t key;
	int semid;	
	pid_t cpid;

	key = ftok(SEM_SET_PATHNAME,SEM_SET_PRO_ID);
	if(key == -1){
		perror("[ERROR] ftok():" );
		exit(EXIT_FAILURE);
	}
	//2、创建信号量集合
	semid=semget(key,1,IPC_CREAT|0644);
	if(key == -1){
		perror("[ERROR] semget():" );
		exit(EXIT_FAILURE);
	}
	/*
	cpid = fork();
	if(cpid == -1){
	
		perror("[ERROR] fork():");
		exit(EXIT_FAILURE);
	}
	if(cpid == 0 ){//子进程
		
		for(;;){
			while(P==1){};
			P==1;
			printf("----------------------------------\n");
			printf("C start.\n");
			printf("C end. \n");
			printf("----------------------------------\n");
			P=0;
		}

	}
	else if(cpid > 0){//父进程
		for(;;){
			while(P == 1){};
			P=1;
			printf("----------------------------------\n");
			printf("F start.\n");
			sleep(1);
			printf("F end. \n");
			printf("----------------------------------\n");
			P=0;
		}
		wait(NULL);
	
	}
	*/

	return 0;
}

查看

root@wangjudealy:/learn# ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x6703f601 0          root       644        1         

初始化 semctl

在这里插入图片描述

操作信号量 semop

在这里插入图片描述

代码

实现互斥

sem.h

#ifndef __SEM_H_
#define __SEM_H_

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

#endif
//create and init  sem
extern int sem_create(int nsems,unsigned short values[]);
//opteion sem
extern int sem_p(int semid , int semnum);
//free sem
extern int sem_v(int semid,int semnum);
//delete sem
extern int sem_del(int semid);

sem.c

#include "sem.h"


#define SEM_SET_PATHNAME "."
#define SEM_PRO_ID 99

union semnu{
	unsigned short *array;//SETALL
};
int sem_create(int nsems,unsigned short values[]){
	key_t key;
	int semid,ret;
	union semnu s;
	key = ftok(SEM_SET_PATHNAME,SEM_PRO_ID);
	if(key == -1){
		perror("[ERROR] ftok():");
		return -1;
	}
	semid = semget(key,nsems,IPC_CREAT|0644);
	if(semid == -1){
		perror("[ERROR] semget():");
		return -1;
	}
	
	s.array = values;
	ret = semctl(semid,0,SETALL,s);
	if(ret == -1){
		perror("[ERROR] semctl():");
		return -1;
	}
	return semid;

}

int sem_p(int semid , int semnum){
	struct sembuf sops;
	sops.sem_num = semnum;
	sops.sem_op=-1;
	sops.sem_flg=SEM_UNDO;//process end,auto free sem
	return semop(semid,&sops,1);
}

int sem_v(int semid , int semnum){
	struct sembuf sops;
	sops.sem_num = semnum;
	sops.sem_op=1;
	sops.sem_flg=SEM_UNDO;//process end,auto free sem
	return semop(semid,&sops,1);
}


int sem_del(int semid){

	return semctl(semid,0,IPC_RMID,NULL);
}

main.c

#include "sem.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void){
	pid_t cpid;
	int semid;
	unsigned short value[]={1};

	semid = sem_create(1,value);
	if(semid == -1){
		perror("[ERROR] sem_create():");
		exit(EXIT_FAILURE);
	}
	cpid = fork();
	if(cpid == -1){
		perror("[ERROR] fork():");
		exit(EXIT_FAILURE);
	}
	if(cpid == 0 ){//子进程
		
		for(;;){
			sem_p(semid,0);
			printf("----------------------------------\n");
			printf("C start.\n");
			printf("C end. \n");
			printf("----------------------------------\n");
			sem_v(semid,0);
		}

	}
	else if(cpid > 0){//父进程
		for(;;){
			sem_p(semid,0);
			printf("----------------------------------\n");
			printf("F start.\n");
			sleep(1);
			printf("F end. \n");
			printf("----------------------------------\n");
			sem_v(semid,0);
		}
		wait(NULL);
	
	}

	return 0;
}

makefile

all:
	@gcc main.c sem.c -o main

结果

F start.
F end. 
----------------------------------
----------------------------------
C start.
C end. 
----------------------------------
----------------------------------
F start.
F end. 
----------------------------------
----------------------------------
C start.
C end. 
----------------------------------
----------------------------------
F start.
F end. 
----------------------------------
----------------------------------
C start.
C end. 
----------------------------------
----------------------------------
F start.
F end. 
----------------------------------

实现信号量同步

线程

一个进程可以创建多个线程,多个线程共享父进程的内存空间
线程共享资源:进程地址空间,文件描述符表,每种信号的处理方式,当前工作目录,用户id和组id
线程独立资源:线程栈,线程上下文信息,线程ID,寄存器的值,errno变量,信号屏蔽字,调度优先级

线程相关命令

pidstat -t -p pid

-t:显示指定进程关联线程
-p:指定进程pid

top -H -p pid
ps -T -p pid

线程的创建

pthread_create 创建一个线程

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
pthread_t:线程ID变量指针
const pthread_attr_t:线程属性设置,可谓NULL
start_routine:线程执行函数指针
arg:线程执行函数参数
return:成功返回0,失败返回错误代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <pthread.h>


//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//线程执行函数,直接由操作系统调度执行
void* do_thread(void *arg){
        printf("\033[43mThread start.\033[0m\n");
}
int main(void){
        pthread_t tid;
        int err;

        err = pthread_create(&tid,NULL,do_thread,NULL);
        if(err != 0){
                fprintf(stderr,"[ERROR] pthread_create(): %s \n",strerror(err));
                exit(EXIT_FAILURE);
        }
        while(1){}
        return 0;
}

root@wangjudealy:/learn/day15# gcc thread_test.c -lpthread
root@wangjudealy:/learn/day15# ./a.out 
Thread start.
^C

问题:
1、编译时独立链接pthread库
2、如果主进程立即结束,会导致线程结束来不及执行

线程的退出 pthread_exit

#include <pthread.h>
void pthread_exit(void *retval);
Compile and link with -pthread.
retval:线程返回值
return: success 0 else -1

线程等待

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

改进代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <pthread.h>


//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//线程执行函数,直接由操作系统调度执行
void* do_thread(void *arg){
        printf("\033[43mThread start.\033[0m\n");

        pthread_exit(NULL);//线程退出,释放资源
}
int main(void){
        pthread_t tid;
        int err;

        err = pthread_create(&tid,NULL,do_thread,NULL);
        if(err != 0){
                fprintf(stderr,"[ERROR] pthread_create(): %s \n",strerror(err));
                exit(EXIT_FAILURE);
        }
        pthread_join(tid,NULL);//等待子线程退出,等待时阻塞主进程
        return 0;
}

效果

root@wangjudealy:/learn/day15# gcc thread_test.c -lpthread
root@wangjudealy:/learn/day15# ./a.out 
Thread start.
root@wangjudealy:/learn/day15# 

线程分离

可结合线程:能够被其他线程回收资源并杀死,在不被其他线程回收前,他的资源不释放。线程创建默认状态是可结合的,可由其他线程调用pthread_join函数等待子线程退出并释放相关资源
可分离进程:不能被其他线程回收或者杀死,该线程资源在终止时由系统来释放

pthread_detach 线程分离,该线程资源在终止时由系统来释放,主线程不阻塞

创建多个线程

由主进程统一创建、释放资源或者分离进程,不要递归创建

多个线程做相同任务

可以通过循环多次创建

多个线程做不同任务

创建多个线程函数

线程间通讯

进程实现同步的方式也适用于线程之间

主进程向线程传递参数

pthread_create 的第四个参数 arg 可以向线程传递参数

线程间的互斥

线程互斥锁

pthread_mutex_t 类型的变量
pthread_mutex_t v;
v =1 可以访问 v=0,不能访问(类似于 value=1的信号量)
使用时 使用lock函数
int pthread_mutex_lock(pthread_mutex_t *v);
使用完需要释放锁
int pthread_mutex_unlock(pthread_mutex_t *v);

static pthread_mutex_t v = PTHREAD_MUTEX_INITIALIZER; //全局变量,静态初始化
//其他代码
pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
//访问临界资源
 pthread_mutex_unlock(&v);//释放锁
pthread_mutex_t 类型的变量的初始化

1、静态初始化
pthread_mutex_t P_LOCK= PTHREAD_MUTEX_INITIALIZER;
2、动态初始化
涉及两个函数 pthread_mutex_init pthread_mutex_destory

static pthread_mutex_t v;
//其他代码

//初始化

pthread_mutex_init(&v,NULL);
//使用锁
//...............

//销毁锁
ptherad_mutex_destory(&v);

线程的同步

1、信号量
2、条件变量(生产者和消费者模型)
  基于互斥锁实现生产者和消费者模型

/*消费者*/
for(;;){
	pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
	if(conditional statement){//消费
	//访问临界资源
	}
	pthread_mutex_unlock(&v);//释放锁
	if(conditional statement){
		//是否继续消费
		break;
	}
}


/*生产者*/
for(;;){
	pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
	if(conditional statement){//生产
	//访问临界资源
	}
	pthread_mutex_unlock(&v);//释放锁
	if(conditional statement){
		//是否继续生产
		break;
	}
}

需要记录生产总数和消费总数,消费和生产总数要对应的上

  基于条件变量实现生产者和消费者模型
  条件变量:允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知
pthread_cond_t类型变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞在这个条件变量上的线程
静态初始化
动态初始化

static pthread_mutex_t v = PTHREAD_MUTEX_INITIALIZED;
static pthread_cond_t  cond = PTHREAD_COND_INITLIZED;
/*消费者*/
for(;;){
	pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
	while{//不满足条件
		pthread_cond_wait(&cond,&v);//1、解锁  2、阻塞在条件变量上  3、在条件变量收到唤醒信号,重新竞争锁
	}
	while(conditional statement){//满足条件,消费
	//访问临界资源
	}
	pthread_mutex_unlock(&v);//释放锁
	if(conditional statement){
		//是否继续消费
		break;
	}
}

/*生产者*/
for(;;){
	pthread_mutex_lock(&v);//获取锁,取得访问临界资源的资格
	if(conditional statement){//生产
	//访问临界资源
	}
	pthread_mutex_unlock(&v);//释放锁
	//生产
	pthread_cond_signal(&cond);//唤醒
	if(conditional statement){
		//是否继续生产
		break;
	}
}

io模型

io的本质是基于操作系统接口来控制底层硬件之间的数据传输,并且在操作系统中实现了多种不同的IO模型。
常用的几种由:
1、阻塞型IO
2、非阻塞型IO
3、多路复用IO

阻塞型IO

在这里插入图片描述

非阻塞型IO

在这里插入图片描述

实现非阻塞IO

设置一般由两种方式
1、通过fcntl函数进行设置
2、通过open、函数在打开文件时设置

fcntl

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
        int flags;
        char buffer[16]={0};
        flags = fcntl(0,F_GETFL);  //获取标准输入的文件状态
        flags = O_NONBLOCK;//追加非阻塞标志
        fcntl(0,F_SETFL,flags); //重新设置标志

        while(1){ // 不断循环
                fgets(buffer,sizeof(buffer),stdin);
                printf("buffer : %s \n",buffer);
        }

        return 0;
}

多路复用IO

在这里插入图片描述
在这里插入图片描述
多路复用方案:
1、select
2、poll
3、epoll

select多路复用

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

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>


int main(void)
{
	int ret;
	int maxfd = 0;
	fd_set readfds,tmpfds;	
	struct timeval tv = {3,0},tmp_tv;
	char buffer[64] = {0};

	FD_ZERO(&readfds);	
	FD_SET(0,&readfds);// 将标准输入读文件描符合集合中

	for(;;){
		tmp_tv = tv;	
		tmpfds = readfds;	

		ret = select(maxfd + 1,&tmpfds,NULL,NULL,&tmp_tv);
		if (ret  == -1){

			perror("[ERROR] select(): ");
			exit(EXIT_FAILURE);

		}else  if (ret == 0){ //  超时返回

			printf("Timeout.\n");

		}else if (ret > 0){ 

			if (FD_ISSET(0,&tmpfds)){  // 判断是否在集合中

				fgets(buffer,sizeof(buffer),stdin);			
				printf("buffer : %s ",buffer);

			}
		}
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值