Linux多进程、线程间通信:共享内存、信号量、管道、互斥锁

嵌入式Linux中编写一应用程序,该程序包含三个进程,每个进程中包含两个线程,采用共享内存、信号量、管道等通信方式实现进程间的通信、同步、互斥操作。

文章末尾附全部代码~

进程、线程的创建可参考前一篇博客:多进程、线程创建管理及状态切换-CSDN博客

运行全过程:

创建过程:

1、信号量创建

(1)创建两个有名信号量sem_read和sem_write,一个用于读取共享内容,一个用于写入。

(2)sem_read是在程序内部使用的变量名,用来操作信号量的描述符。

(3)mysem_read是系统中标识信号量的名字,用于跨进程或跨线程的信号量通信和同步。

(4)因为在该程序中,信号量是在父进程中创建的,所以所有的子进程和线程都可以直接用sem_read来使用信号量。它们共享父进程创建的资源(文件描述符:文件、管道、信号量等)。

2、共享内存创建

(1)互斥锁和共享内存是进程的内部资源,不会在父进程和子进程中继承

(2)子进程若是想访问父进程创建的共享内存和互斥锁,需要通过进程间通信来实现。如子进程用shmat函数连接到共享内存区域,获取共享内存地址。

3、管道创建

(1)管道是常用的进程间通信机制,运行一个进程将输出发送给另一个进程。

(2)创建匿名管道必须在创建子进程之前,否则子进程将无法复制。

(3)int pipefd[2]:pipefd[0]对应管道的读端,pipefd[1]对应管道的写端。pipefd为管道的文件描述符。

4、互斥锁创建

互斥锁是常用的同步机制,用于保护共享资源,防止多个线程或进程同时访问或修改同一份数据时发生冲突。

5、管道通信

(1)父进程(往管道写入数据):关闭读端口(不用的端口),写入数据“Hello pipe! Communication success”,关闭写端口

(2)子进程0(从管道读取数据):关闭写端口(不用的端口),读取数据“Hello pipe! Communication success”,关闭写端口

(3)管道在父进程内创建,所以父子进程能通过文件描述符共享同一个管道,进行通信。

运行情况:

6、信号量同步通信,互斥锁互斥通信(模拟动车抢票系统)

在信号量通信中往共享内存中输入动车票的票数,在互斥锁通信中,让三个子线程模仿三 个人来抢票。

6.1信号量同步通信:写入(共享内存)系统中剩余的动车票票数

父进程:

(1)申请写信号量sem_write,信号量值-1

(2)写入共享内存:系统中剩余的动车票数

(3)给读信号量sem_read值+1

子进程1:

(1)申请读信号量sem_read,为0则等待。申请成功后,信号量值-1

(2)读取共享内存:系统中剩余的动车票数

(3)给写信号量sem_write值+1

使用读、写信号量完成同步。

6.2互斥锁互斥通信:创建的三个子线程互斥访问共享内存抢票。

(1)上锁,拿不到锁阻塞

(2)if判断是否还有票:

a. 是:退出线程

b. 否:抢到了票,共享内存中票数-1

(3)释放锁

(4)依次循环直至共享内存中没有余票

线程执行函数:

对共享内存资源的访问可以*shmid_addr,访问地址中存放的值。

运行情况:

6.3子线程0可能会先退出系统

因为子进程0的子线程没有让它申请信号量阻塞等待,只是用了sleep(3)来令他等待票务系统中的票数设置。

若是票未能及时输入(输入晚了),子进程0的子线程查询的时候没有票,会直接退出。

这样子就只剩下父进程和子进程1的线程在抢票了。

若是晚输入几s,子进程0此时已经接近执行完sleep(3),而子进程1和父进程才刚刚开始执行sleep(3),所以一开始只有子进程0的子线程自己在抢票。

只有立刻输入票数总数,会看到3个子线程几乎同时开始抢票。

但都会是子进程0抢到第一张。(因为它先运行完sleep(3))

7、资源共享

信号量、管道:都在父进程创建,子进程共享父进程文件描述符资源,可以直接调用。

共享内存:在父进程中已经完成共享内存段连接到进程的地址空间,由于子进程继承父进程的地址空间,子进程中可以通过地址直接使用共享内存,无需重新连接共享内存段。

互斥锁在父进程中初始化了互斥锁 g_mutex_lock,这使得该互斥锁可以在父进程和其创建的子进程中使用。并且在多线程环境中,线程共享同一进程的地址空间和资源,因此可以共享同一个互斥锁。

互斥锁可被视为共享资源中的一种。

相关函数

1、信号量相关函数

sem_open():

       创建信号量。

第一个参数为信号量的外部名字,第二个参数指定创建或打开信号量的方式和标志位,第三个参数为权限位,第四个参数为信号量的初值。

1)信号量名字为:mysem_read

2)在打开文件时,如果文件不存在则创建它O_CREAT

3)以可读可写的方式打开:O_RDWR

4)所有用户都有读写权限,但没有执行权限:0666

5)信号量初值为:0

6)返回值为SEM_FAILED时,表明创建失败。成功则返回一个信号量描述符,表示该信号量

sem_wait()函数:

阻塞等待指定信号量的值为1后,减1返回

sem_post()函数:

对指定信号量的值,加1返回

2、共享内存相关函数:

shmget()函数:

用于创建共享内存段或获取已存在的共享内存段的标识符。

第一个参数指定id值,第二个参数指定共享内存的大小,第三个参数为一组标志。

(1)键值,用于标识共享内存段:key

(2)共享内存段大小:MAXSIZE

(3)共享内存的权限标志,所有用户具有读写权限:0666

4)创建标志,表示如果共享内存不存在,则创建它;如果共享内存已存在,则返回其标识符给shmidIPC_CREAT

shmat()函数:

将已存在的共享内存段连接到当前进程的地址空间中

第一个参数为shmget返回的共享内存标识,第二个参数指定共享内存连接到当前进程中的地址位置,通常为空,第三个参数为标志位。

(1)共享内存段标识符(要连接的共享内存块):shmid

(2)指定共享内存连接的地址,通常设置为:NULL,表示由系统选择适当的地址进行连接。

3)连接标志,通常为‘0’,表示默认行为:0

4)返回值:连接成功,返回共享内存段的起始地址,失败则返回值为‘(void*-1

3、管道相关函数:

pipe()函数:int pipe(int pipefd[2]);

创建管道,实现进程间的通信

(1)pipefd是一个长度为2的整型数组,在调用 pipe() 函数后用于存储管道的文件描述符。

(2)pipefd[0] 用于读取管道数据,称为读端(读文件描述符)

(3)pipefd[1] 用于写入管道数据,称为写端(写文件描述符)

close()函数:

关闭管道中指定的端口

write()函数:

往管道中写入数据,如果管道数据满了,那么write一直等待,直到有数据被读取出去。

       第一个参数为写入端文件描述符,第二个参数为要写入管道的数据指针,第三个参数为写入数据的长度

read()函数:

读取管道中的数据,如果管道没有数据,那么read一直等待,直到有数据。

第一个参数为管道读取端文件描述符,第二个参数为存储从管道中读取的数据的缓冲区,第三个参数为要读取的最大字节数(缓冲区能容纳的最大字节数)。

4、互斥锁相关函数:

pthread_mutex_init():

       是以动态方式创建互斥锁的。第一个参数为互斥锁变量的指针。第二个参数为互斥锁的属性设置。

(1)互斥锁对象(变量)的地址:&g_mutex_lock

(2)互斥锁属性,NULL表示默认属性:NULL

(3)返回值:成功,‘ret’值为0;失败,‘ret’为非零的错误码。

g_mutex_lock是一个互斥锁对象。

(1)pthread_mutex_lock(pthread_mutex_t *mutex):

mutex 没有被上锁,当前线程会将锁锁上,如果 mutex 被上锁,当前线程会阻塞,直至被解锁后,解除阻塞。

(2)pthread_mutex_trylock(pthread_mutex_t *mutex):

mutex 没有被上锁,当前线程会将锁锁上,如果 mutex 被上锁,直接返回,不阻塞。

代码

/*******************************************1、引入函数库***********************************************/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <string.h>
#include <errno.h>
#include<assert.h>
#include<pthread.h>  
#include<unistd.h>
//pthread用户库内没有gettid(),所以需要下面的头文件以及gettid()函数定义
#include<sys/syscall.h>
#include <semaphore.h>
//信号量函数库
#include <fcntl.h>
#include <errno.h>
//共享内存函数库
#include <sys/ipc.h>
#include <sys/shm.h>
/*******************************************2、函数声明***********************************************/
//进程创建函数
void creat_child_process();             // 创建子进程
//线程创建函数
pid_t gettid();                         // 线程号获取函数
void *parent_thread();                  // 父进程的线程体
void *child_pid0_thread();              // 子进程0的线程体
void *child_pid1_thread();              // 子进程1的线程体
void create_parent_thread();            // 创建父进程的线程
void create_child_pid0_thread();        // 创建子进程0的线程
void create_child_pid1_thread();        // 创建子进程1的线程
//共享内存创建函数
void create_shared_memory();
/*******************************************3、全局变量定义***********************************************/
//全局变量定义
pid_t pid;                           // 进程结构体
int child_pid[3];                    // 子进程PID
int i = 0;
int parent_pid = 0;                  // 父进程PID
int rc,old_scheduler_policy;         // 进程状态查看
struct sched_param my_params;        // 线程的优先级
sem_t *sem_read,*sem_write;          // 定义信号量
key_t key;
int MAXSIZE=1024;                    // 输入字符串最大长度
int shmid;                           // 创建共享内存
int *shmid_addr;                     // 共享内存地址 
int pipefd[2] = { 0 };               // 定义传出参数
static pthread_mutex_t g_mutex_lock; // 定义互斥锁数据结构
int ret;
/*********************************************4、主函数***********************************************/
int main()
{
    printf("*******************进程系统启动*******************\n");
	// 创建信号量
	sem_read = sem_open("mysem_read", O_CREAT | O_RDWR , 0666, 0);  // 创建一个名为"mysem_read"的读信号量(有名信号量)
	if (sem_read == SEM_FAILED)
	{
			printf("errno=%d\n", errno);
			return -1;                                              // 创建失败则返回-1退出
	}
	sem_write = sem_open("mysem_write", O_CREAT | O_RDWR, 0666, 1); // 创建一个名为"mysem_write"的写信号量(有名信号量)
	if (sem_write == SEM_FAILED)
	{
			printf("errno=%d\n", errno);
			return -1;                                              // 创建失败则返回-1退出
	}
	// 创建共享内存
    // 创建共享内存区域,失败则返回-1
	if ((shmid = shmget(key, MAXSIZE, 0666 | IPC_CREAT)) == -1)     // 创建共享内存
	{
        perror("semget");
        exit(-1);                                                  
	}	
	// 获取共享内存区域地址,失败则返回-1
	if ((shmid_addr = (int *)shmat(shmid, NULL, 0)) == (int *)(-1))  // 将进程连接到共享内存的地址空间
	{
        perror("shmat");
        exit(-1);                                                  
	}
	// 创建管道
	if (pipe(pipefd) < 0)
	{
		perror("pipe error");
		return -1;                                                  // 失败则返回-1退出
	}
    // 创建互斥锁
    ret = pthread_mutex_init(&g_mutex_lock, NULL);
    if (ret != 0) {
        printf("mutex init failed\n");
        return -1;                                                  // 失败则返回-1退出
    }
    printf("进程初始化:\n");
	// 获取进程的PID号,子进程为父进程号+1
    parent_pid = getpid();               
    child_pid[0] = parent_pid+1;
    child_pid[1] = parent_pid+2;
    printf("I'm start parent, pid: %d, ppid: %d\n", getpid(), getppid());
    // 创建两个子进程
    for(;i < 2;i++)
    {
        creat_child_process(i);     // 调用子进程创建函数
        if (pid==0)                 // 为子进程时
        {
            break;                  // 退出子进程的整个for循环,否则子进程又会继续创建子进程
        }       
    }
    sleep(1);                       // 等待子进程创建完成
    /****************************************************管道通信********************************************************/
    if (parent_pid==getpid())
    {
        printf("*******************开启管道通信*******************\n");
        printf("I'm Parent_pid  的主线程[%d],写入管道:\nHello pipe! Communication success!\n",gettid());  // 获取所属线程号
        close(pipefd[0]);            // 关闭读端,关闭不用的端口
        char *ptr = "Hello pipe! Communication success!";
        write(pipefd[1],ptr,strlen(ptr));         // 如果管道数据满了,那么write一直等待,直到有数据被读取出去。
        close(pipefd[1]);                                         // 关闭写端                                          
    }
    if (child_pid[0]==getpid())
    {
        close(pipefd[1]);                                         // 关闭写端,关闭不用的端口
        char buff[1024] = { 0 };
        read(pipefd[0], buff, 1024);             // 如果管道没有数据,那么read一直等待,直到有数据;
        printf("I'm Child_pid[0]的主线程[%d],读出管道:\n%s\n",gettid(),buff);                             // 获取所属线程号
        close(pipefd[0]);                                         // 关闭读端                   
    }
    sleep(3);
    /****************************************************信号量通信********************************************************/
	if (parent_pid==getpid())
	{
        printf(" -------------------模拟动车抢票系统------------------\n");
        printf("******************开启信号量同步通信*******************\n");
		sem_wait(sem_write);            // 阻塞等待写信号量的值为1后,减1返回
		printf("I'm Parent_pid  的主线程[%d]:获取写信号量\n请输入系统中动车票的总数:\n",gettid());
        scanf("%d", shmid_addr);
		sem_post(sem_read);             // 读完之后设置读信号量,加1返回
	}
	if (child_pid[1]==getpid())
	{
		sem_wait(sem_read);             // 阻塞等待读信号量的值为1后,减1返回
		printf("I'm Child_pid[1]的主线程[%d]:获取读信号量\n查看系统中动车票总数:\n%d\n",gettid(),*shmid_addr); //读取共享内存中的值
		sem_post(sem_write);            // 读完之后设置写信号量,加1返回
        printf("******************开启互斥锁互斥抢票*******************\n");
	}
	sleep(3);
	// /*******************************************线程创建***********************************************/
    // 在父进程中创建线程
    if (parent_pid==getpid())
    {
        create_parent_thread();          // 创建线程,线程中使用互斥锁
    }
    // 在两个子进程中创建线程
    if (child_pid[0]==getpid())
    {
        create_child_pid0_thread();     // 创建线程,线程中使用互斥锁
    }
    if (child_pid[1]==getpid())
    {
        create_child_pid1_thread();     // 创建线程,线程中使用互斥锁
    }

	return 0;
}
/*******************************************5、函数定义*********************************************/
/********************************************创建子进程**********************************************/
/*************************************************************************************************************************
	函  数 : creat_child_process(int i)
	函数名 :  创建子进程
	作  用 : 创建父进程中的子进程child[0]、child[1]
*************************************************************************************************************************/
void creat_child_process(int i)
{
    pid = fork();                                   // 子进程中返回子进程的PID号给变量pid
    if(pid == 0)                                    // 该块只在该子进程中执行,写到外面会到父进程中执行
    {
        printf("I'm child_pid[%d] ,PID: %d, PPID: %d\n", i,getpid(),getppid());
    }
    else if(pid<0)                                  // 创建失败fork()返回-1
    {
        printf("fork error!");
        exit(1);                                    // 到主调程序,表示异常退出
    }
}
/********************************************线程创建**********************************************/
// 线程号获取函数
pid_t gettid()
{
     return syscall(SYS_gettid);
}
/*************************************************************************************************************************
	函  数 : *××××××_thread(void *arg)
	函数名 :  线程执行函数
	作  用 :  父进程parent和子进程child[0]、child[1]的子线程执行函数
*************************************************************************************************************************/
// 父进程线程执行函数
void *parent_thread(void *arg)
{
    int index = *(int*)arg;
    // 互斥锁
    while(1)
    {      
        pthread_mutex_lock(&g_mutex_lock);             // 上锁,拿不到锁阻塞
        if(*shmid_addr==0)
        {
            printf("I'm Parent_pid  的子线程[%d]:没抢到票!!!\n",gettid());                   // 获取所属线程号
            exit(0);                                                                        // 没有票后退出线程
        }
        else
        {
            *shmid_addr=*shmid_addr-1;                                                      // 余票-1
            printf("I'm Parent_pid  的子线程[%d]:抢到了票,还剩%d张票\n",gettid(),*shmid_addr);  // 获取所属线程号
        }
        pthread_mutex_unlock(&g_mutex_lock);        // 释放锁
        sleep(1);
    }
}

// 子进程0创建线程
void *child_pid0_thread(void *arg)
{
    int index = *(int*)arg;
    // 互斥锁
    while(1)
    {
        pthread_mutex_lock(&g_mutex_lock);
        if(*shmid_addr==0)
        {
            printf("I'm Child_pid[0]的子线程[%d]:没抢到票!!!\n",gettid());  // 获取所属线程号
            exit(0);
        }
        else
        {
            *shmid_addr=*shmid_addr-1; //余票-1
            printf("I'm Child_pid[0]的子线程[%d]:抢到了票,还剩%d张票\n",gettid(),*shmid_addr);  // 获取所属线程号
        }
        pthread_mutex_unlock(&g_mutex_lock);
        sleep(1);
    }
}

// 子进程1创建线程
void *child_pid1_thread(void *arg)
{
    int index = *(int*)arg;
    // 互斥锁
    while(1)
    {
        pthread_mutex_lock(&g_mutex_lock);
        if(*shmid_addr==0)
        {
            printf("I'm Child_pid[1]的子线程[%d]:没抢到票!!!\n",gettid());  // 获取所属线程号
            exit(0);
        }
        else
        {
            *shmid_addr=*shmid_addr-1; //余票-1
            printf("I'm Child_pid[1]的子线程[%d]:抢到了票,还剩%d张票\n",gettid(),*shmid_addr);  // 获取所属线程号
        }
        pthread_mutex_unlock(&g_mutex_lock);
        sleep(1);
    }
}
/*************************************************************************************************************************
	函  数 : create_×××××_thread(void)
	函数名 : 创建子线程
	作  用 : 创建进程parent、child[0],child[1]的子线程
*************************************************************************************************************************/
void create_parent_thread()
{
    pthread_t id;                                           // 定义线程的数据结构
    int i = 0;
    pthread_create(&id,NULL,parent_thread,(void*)&i);       // 创建线程,新建线程执行的函数为parent_thread         
    pthread_join(id,NULL);  //等待线程结束
}
void create_child_pid0_thread()
{
    pthread_t id;                                           // 定义线程的数据结构
    int i = 0;
    pthread_create(&id,NULL,child_pid0_thread,(void*)&i);   // 创建线程,新建线程执行的函数为parent_thread       
    pthread_join(id,NULL);                                  //等待线程结束
}
void create_child_pid1_thread()
{
    pthread_t id;                                           // 定义线程的数据结构
    int i = 0;
    pthread_create(&id,NULL,child_pid1_thread,(void*)&i);   // 创建线程,新建线程执行的函数为parent_thread       
    pthread_join(id,NULL);                                  //等待线程结束
}
/********************************************创建共享内存*********************************************/
void create_shared_memory()
{
    // 创建共享内存区域,失败则返回-1
	if ((shmid = shmget(key, MAXSIZE, 0666 | IPC_CREAT)) == -1)
	{
        perror("semget");
        exit(-1);
	}	
	// 获取共享内存区域地址,失败则返回-1
	if ((shmid_addr = (int *)shmat(shmid, NULL, 0)) == (int *)(-1))  // 共享内存数据为int型
	{
        perror("shmat");
        exit(-1);
	}
}

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值