Linux——进程并发控制(系统中的POSIX信息量机制、进程间通信)

目录

一、Linux系统中POSIX信号量机制

1、POSIX有名信号量 

(1)常用函数

(2)有名信号量应用于多线程的例子

(3)有名信号量应用于多进程

2、POSIX无名信号量

(1)常用函数

(2) 无名信号量实现一个进程的各个线程间的互斥

(3)无名信号量实现各进程间的互斥

(4) 信息量的共享内存

 二、Linux系统中的进程间通信

1、Signal(信号)机制

2、消息队列

(1)发送进程

(2)参数 

3、共享内存

(1)函数

(2)参数定义

4、管道机制

(1)有名管道

(2)无名管道 

一、Linux系统中POSIX信号量机制

POSIX有两种信号量实现机制:

  • 无名信号量:基于内存操作
  • 有名信号量:基于文件操作

1、POSIX有名信号量 

(1)常用函数

  • sem_open()  //创建并初始化有名信号量
  • sem_close()  //关闭有名信号量
  • sem_unlink()  //从系统中删除信号量
  • sem_getvalue()  //测试信号量
  • sem_wait()/sem_trywait()  //等待共享资源
  • sem_post()  //挂出共享资源
sem_t *sem_open
(
const char *name, //信号量的外部名字
int oflag,  //选择创建或打开一个现有的信号量
mode_t mode,  //权限位
unsigned int value //信号量初始值
);

//Posix有名信号量的值是随内核持续的。也就是说,一个进程创建了一个信号量,这个进程结束后,这个信号量还存在,并且信号量的值也不会改变。
如:
# ./semopen test
# ./semgetvalue test
value=1 //信号的值仍然是1

//当持有某个信号量锁的进程没有释放它就终止时,内核并不给该信号量解锁。
#./semopen test
#./semwait test&
pid 1834 has semaphore,value=0
#./semgetvalue test
value=0  //信号量的值变为0了

(2)有名信号量应用于多线程的例子

#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pthread.h>
 
void *thread_function(void *arg); /*线程入口函数*/
void print(pid_t); /*共享资源函数*/
sem_t *sem; /*定义Posix有名信号量*/
int val; /*定义信号量当前值*/

int main(int argc,char *argv[])
{
  int n=0;
  if(argc!=2)
  {
      printf("please input a file name!\n");
      exit(1);
  }
  sem=sem_open(argv[1],O_CREAT,0644,3); /*打开一个信号量*/
  while(n++<5) /*循环创建5个子线程,使它们同步运行*/
  {
    if((pthread_create(&a_thread,NULL,thread_function,NULL))!=0)
        {
             perror("Thread creation failed");
             exit(1);
         }
  }
  pthread_join(a_thread,NULL); //等待线程结束,类似于wait()
  sem_close(sem);
  sem_unlink(argv[1]);
}

void *thread_function(void *arg)
{
    sem_wait(sem); /*申请信号量*/
    print(); /*调用共享代码段*/
    sleep(1); //滞留1s
    sem_post(sem); /*释放信号量*/
    printf("I’m finished,my tid is %d\n",pthread_self());
}
void print() //模拟的共享代码
{
  printf("I get it,my tid is %d\n",pthread_self()); //显示当前线程号
  sem_getvalue(sem,&val);
  printf("Now the value have %d\n",val); //显示信号量值
}

(3)有名信号量应用于多进程

#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>#
#include<stdlib.h>
#include <fcntl.h>
void print(pid_t);
sem_t *sem; /*定义Posix有名信号量*/
int val; /*定义信号量当前值*/

int main(int argc,char *argv[])
{
  int n=0;
  if(argc!=2)
  {
      printf("please input a file name!\n");
      exit(1);  
  }
  sem=sem_open(argv[1],O_CREAT,0644,2); /*打开一个信号量, 初值设为2*/
  while(n++<5) /*循环创建5个子进程,使它们同步运行*/
  {
    if(fork()==0) 
    {
        sem_wait(sem); /*申请信号量*/
        print(getpid()); /*调用共享代码段*/
        sleep(1); 
        sem_post(sem); /*释放信号量*/
        printf("I’m finished,my pid is %d\n",getpid());
        return 0; 
    }
  }
  wait(); /*等待所有子进程结束*/
  sem_close(sem);
  sem_unlink(argv[1]);
  exit(0);
}

void print(pid_t pid)
{
  printf("I get it,my pid is %d\n",pid);
  sem_getvalue(sem,&val);
  printf("Now the value have %d\n",val);
}

运行结果:

2、POSIX无名信号量

 Posix提供的无名信号量是基于内存的信号量,它们由应用程序分配信号量的内存空间,然后由系统初始化它们的值。

(1)常用函数

int sem_init(sem_t *sem,int shared,unsigned int value);
int sem_getvalue(sem_t *sem);
基于内存的信号量是由sem_init初始化的。sem参数指向必须由应用程序分配的sem_t变量。
shared为0,待初始化的信号量是在同一进程的各个线程共享的,否则该信号量是在进程间共享的。
shared为非0,该信号量必须存放在即将使用它的所有进程都能访问的某种类型的共享内存中。跟sem_open一样,value参数是该信号量的初始值。
 使用完一个基于内存的信号量后,调用sem_destroy关闭它。
除了sem_open和sem_close外,其它的poisx有名信号量函数都可以用于基于内存的信号量。 

(2) 无名信号量实现一个进程的各个线程间的互斥

#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
void *thread_function(void *arg); /*线程入口函数*/
void print(void); /*共享资源函数*/
sem_t bin_sem; /*定义信号量*/
int value; /*定义信号量的灯*/

int main()
{
  int n=0;
  pthread_t a_thread; 
  if((sem_init(&bin_sem,0,2))!=0) /*初始化信号量,初始值为2*/
  {
      perror("sem_init");
      exit(1);
  }
  while(n++<5) /*循环创建5个线程*/
  {
    if((pthread_create(&a_thread,NULL,thread_function,NULL))!=0) 
    {
      perror("Thread creation failed");
      exit(1);
    }
  }
  pthread_join(a_thread,NULL);/*等待子线程返回*/
}

void *thread_function(void *arg)
{
  sem_wait(&bin_sem); /*等待信号量*/
  print();
  sleep(1);
  sem_post(&bin_sem); /*唤起信号量*/
  printf("I finished,my pid is %d\n",pthread_self());
  pthread_exit(arg);
}
void print()
{
  printf("I get it,my tid is %d\n",pthread_self());
  sem_getvalue(&bin_sem,&value); /*获取信号量的值*/
  printf("Now the value have %d\n",value);
}

运行结果:

 

(3)无名信号量实现各进程间的互斥

#include <semaphore.h>
#include <unistd.h>
#include<stdlib.h>
#include <stdio.h>
void print(pid_t);
sem_t bin_sem; /*定义Posix信号量*/
int val; /*定义信号量当前值*/


int main() 
{
  int n=0;
  if((sem_init(&bin_sem,0,3))!=0) /*初始化信号量,初始值为3*/
  {
    perror("sem_init");
    exit(1);
  }
  sem_getvalue(&bin_sem,&val); /*查看信号量的值*/
  printf("The value have %d\n",val);
  while(n++<5) /*循环创建5个子进程,使它们同步运行*/
  {
    if(fork()==0) 
    {
        sem_wait(&bin_sem); /*申请信号量*/
        print(getpid()); /*调用共享代码段*/
        sleep(1); 
        sem_post(&bin_sem); /*释放信号量*/
        printf("I’m finished,my pid is %d\n",getpid());
        return 0; 
    }
  }
  wait(); /*等待所有子进程结束*/
  return 0;
}

void print(pid_t pid)
{
  printf("I get it,my pid is %d\n",pid);
  sem_getvalue(&bin_sem,&val);
  printf("Now the value have %d\n",val);
}

 运行结果:

(4) 信息量的共享内存

问题在于sem信号量不在共享内存区中。fork出来的子进程通常不共享父进程的内存空间。子进程是在父进程内存空间的拷贝上启动的,它跟共享内存不是一回事。

  • sem_open不需要shared参数,有名信号量总是可以在不同进程间共享的。
  • sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号量的值。因此,对于一个给定的信号量,我们必须小心保证只调用一次sem_init。
  • sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。
  • posix有名信号量是通过内核持续的,一个进程创建一个信号量,另外的进程可以通过该信号量的外部名(创建信号量使用的文件名)来访问它。 posix基于内存的信号量的持续性却是不定的,如果基于内存的信号量是由单个进程内的各个线程共享的,那么该信号量就是随进程持续的,当该进程终止时它也会消失。 如果某个基于内存的信号量是在不同进程间同步的,该信号量必须存放在共享内存区中,只要该共享内存区存在,该信号量就存在。
  • 基于内存的信号量应用于进程很麻烦,而有名信号量却很方便,基于内存的信号量比较适合应用于一个进程的多个线程。

 二、Linux系统中的进程间通信

通信方法:

  • 信号量---semaphore
  • 信号---signal
  • 共享内存---share memory
  • 消息队列---message queque
  • 管道---pipe
  • 套接字---socket 

①信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

②信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数signal外,还支持语义符合Posix.1标准的信号函数sigaction;

 ③管道(Pipe)及有名管道(FIFO):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;FIFO是全双工的管道,管道是半双工的。

④消息队列(message queue):消息队列是消息的链接表,包括Posix消息队列和system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

⑤共享内存(Share memory):使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

⑥套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

拓展:

  • 最初Unix IPC包括:管道、FIFO、信号;
  • System V IPC包括:System V消息队列、System V信号量、System V共享内存区;
  • Posix IPC包括: Posix消息队列、Posix信号量、Posix共享内存区。

1、Signal(信号)机制

        Signal机制可以被理解成进程的软中断,因此,在实时性方面还是相对比较高的。进程控制块中设计了一个signal的位图信息(在signal.h中定义),其中的每位与具体的signal相对应,这与中断机制是保持一致的。

       当系统中一个进程A通过signal系统调用向进程B发送signal时,设置进程B的对应signal位图,类似于触发了signal对应中断。发送signal只是“中断”触发的一个过程,具体执行会在两个阶段发生:

1、  system call返回。进程B由于调用了system call后,从内核返回用户态时需要检查他拥有的signal位图信息表,此时是一个执行点。

2、  中断返回。进程被系统中断打断之后,系统将CPU交给进程时,需要检查即将执行进程所拥有的signal位图信息表,此时也是一个执行点。

2、消息队列

       消息队列为进程提供了一种异步传递消息的方法。在使用msgget()建立了一条消息队列之后,发送进程和接收进程就可以通过这条消息队列交换消息。

(1)发送进程

发送进程将消息发送到指定的消息队列,而接收者试图从指定的消息队列中获取消息。如果该队列中没有消息的话,则接收者根据自己是否要等待的意愿而阻塞或返回某个标志。

key_t ftok (char*pathname, char proj_id); 

该函数用于为消息队列、信号量、共享存储等IPC机制创建键值。

pathname是一个存在的文件名。

proj_id是子序号,

在实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

int msgget(key_t key, int msgflg)

(2)参数 

参数key是一个键值,由ftok获得;

msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。

在以下两种情况下,该调用将创建一个新的消息队列:

  • 1.如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
  • 2. key参数为IPC_PRIVATE;

 参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果;为0则不创建,只查找已有的消息队列。

调用返回:成功返回消息队列描述字,否则返回-1。

注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将按照随机数创建新的消息队列。

int msgrcv(int msgqid, struct msgbuf *msgp, int msgsz, long msgtype, int msgflg);

该系统调用从msgqid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。

msgqid为消息队列描述字

消息返回后存储在msgp指向的地址

msgsz指定msgbuf的mtext成员的长度(即消息内容的长度)

msgtype为请求读取的消息类型;

3、共享内存

     共享内存提供了一种在进程间高效共享大量数据的方法。共享内存是IPC 机制提供的最重要资源之一,广泛用在许多数据库应用之中。

      System V 的共享内存段由shmget()系统调用创建。在创建共享内存段之后,进程通过shmat()系统调用将自身附加到该共享内存段上,然后就可以对其执行读写操作。进程还可以通过shmdt()系统调用将自身脱离内存段。

 由于共享内存为多个进程提供了一种公共资源,它经常和信号量一起使用以防止发生碰撞。

shmid=shmget(key,size,flag)  //创建、获得一个共享存储区。

  • key是共享存储区的名字;
  • size是其大小(以字节计);
  • flag是用户设置的标志,如IPC_CREAT。

(1)函数

  • shmat

共享存储区的附接。从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。

系统调用格式:   virtaddr=shmat(shmid,addr,flag)

•shmid是共享存储区的标识符;

•addr是用户给定的,将共享存储区附接到进程的虚地址空间;

•flag规定共享存储区的读、写权限,以及系统是否应对用户规定的地址做舍入操作。其值为SHM_RDONLY时,表示只能读;其值为0时,表示可读、可写;其值为SHM_RND(取整)时,表示操作系统在必要时舍去这个地址。v该系统调用的返回值是共享存储区所附接到的进程虚地址virtaddr。

  • shmdt

把一个共享存储区从指定进程的虚地址空间断开。

系统调用格式:     shmdt(virtaddr)

virtaddr是要断开连接的虚地址,亦即以前由连接的系统调用shmat( )所返回的虚地址。

调用成功时,

返回0值,调用不成功,返回-1。

  • shmctl

共享存储区的控制,对其状态信息进行读取和修改。

   shmctl(int shmid,int cmd,struct shmid_ds *buf)

(2)参数定义

         参数定义,buf是用户缓冲区地址,cmd是操作命令。命令可分为多种类型:

(1)IPC_STAT用于查询有关共享存储区的情况。如其长度、当前连接的进程数、共享区的创建者标识符等;

(2)IPC_SET 用于设置或改变共享存储区的属性。如共享存储区的许可权、当前连接的进程计数等;

(3)SHM_LOCK /SHM_UNLOCK对共享存储区的加锁和解锁命令;

(4)IPC_RMID 删除共享存储区标识符等。

状态查询是将shmid所指示的数据结构中的有关成员,放入所指示的缓冲区中;

设置是用由buf所指示的缓冲区内容来设置由shmid所指示的数据结构中的相应成员。

4、管道机制

      所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者—消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。

(1)有名管道

     有名管道是一个可以在文件系统中长期存在的、具有路径名的文件。

     用系统调用mknod建立。可让更多的进程也能利用管道进行通信。其它进程可以知道它的存在,并能利用路径名来访问该文件。对有名管道的访问方式与访问其他文件一样,需先用open( )打开。

(2)无名管道 

     无名管道是一个临时文件。利用pipe建立起来的无名文件(无路径名)。

     只用该系统调用所返回的文件描述符来标识该文件,故只有调用pipe的进程及其子孙进程才能识别此文件描述符,才能利用该文件(管道)进行通信。

当这些进程不再使用此管道时,核心收回其索引结点。

两种管道的读写方式是相同的。

pipe文件的建立:

  • 分配磁盘和内存索引结点
  • 为读进程分配文件表项
  • 为写进程分配文件表项
  • 分配用户文件描述符
  • 读/写进程互斥
  • 内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。

      为使读、写进程互斥地访问pipe文件,需使各进程互斥地访问pipe文件索引结点中的直接地址项。

       因此,每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。操作结束后解锁,并唤醒因该索引结点上锁而睡眠的进程。

1、pipe 建立一无名管道。

    系统调用格式      int pipe(int filedes[2])

    参数定义,filedes[1]是写入端,filedes[0]是读出端。

 2、read

  系统调用格式     int read(int fd,char *buf, unsigned int nbyte)

  功能:从fd所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。

 3、write

系统调用格式       int write(int fd,char *buf,unsigned int nbyte)

功能:把nbyte 个字节的数据,从buf所指向的缓冲区写到由fd所指向的文件中。如文件加锁,暂停写入,直至开锁。参数定义同read。

如有错误,敬请指正。

您的收藏与点赞都是对我最大的鼓励和支持!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sweep-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值