在嵌入式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)创建标志,表示如果共享内存不存在,则创建它;如果共享内存已存在,则返回其标识符给shmid:IPC_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);
}
}