Linux信号的使用和进程间通信

一、信号的使用

1. 信号的基本概念

信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作
在这里插入图片描述

2. 修改信号的响应方式—signal()

在键盘上按下 Ctrl+c 时,会给当前终端前台执行的进程发送 SIGINT 信号,用 signal 修 改 SIGINT 信号的响应方式示例代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include<signal.h>
//信号处理函数,收到信号的时候要执行的函数
void fun_sig(int sig)
{
    printf("sig=%d\n",sig);
    signal(sig,SIG_DFL);
}
int main()
{
    //和内核之间达成的约定,如果收到信号,就调用fun_sig函数处理,
    signal(SIGINT,fun_sig);
    //signal(SIGINT,SIG_IGN);
    while(1)
    {
        printf("hello!\n");
        sleep(1);
    }
}

在这里插入图片描述

在这里插入图片描述

3. 发送信号—kill()

int kill(pid_t pid, int sig);

pid > 0指定将信号发送个那个进程

pid==0信号被发送到和当前进程在同一个进程组的进程

pid == -1将信号发送给系统上有权限发送的所有的进程

pid < -1将信号发送给进程组 id 等于 pid 绝对值,并且有权限发送的所有的进程。

sig指定发送信号的类型。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>

int main(int argc ,char* argv[])
{
    if(argc!=3)
    {
        printf("arg err\n");
        exit(0);
    }
    int pid=0;
    int sig=0;

    sscanf(argv[1],"%d",&pid);
    sscanf(argv[2],"%d",&sig);
    if(kill(pid,sig)==-1)
    {
        perror("kill err");
    }
}

在这里插入图片描述

子进程结束会给父进程发送信号 SIGCHILD

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

void fun_sig(int sig)
{
    printf("sig=%d\n",sig);
    int val=0;
    wait(&val);//方法一处理僵死进程
}
int main()
{
    //signal(SIGCHLD,fun_sig);
    signal(SIGCHLD,SIG_IGN);//方法2处理僵死进程
    pid_t pid=fork();
    assert(pid!=-1);
    if(pid==0)
    {
        sleep(3);
        exit(0);
    }
    for(int i=0;i<5;++i)
    {
        printf("main tun\n");
        sleep(1);
    }
}

二、 进程间通信(IPC)

实现mybash
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<signal.h>
#include<sys/wait.h>
#include<pwd.h>

#define MAX_ARG 10

void print_info()
{
   char *s="$";
   int id=getuid();
   //管理员的id为0
   if(0==id)
   {
       s="#";
   }
   struct passwd* pw=getpwuid(id);
   if(NULL==pw)
   {
       printf("mybash>>$");
       fflush(stdout);
       return;
   }
   char hostname[128]={0};
   if(gethostname(hostname,128)==-1)
   {
       printf("mybash>>$");
       fflush(stdout);
       return;
   }
   char pwd_str[256]={0};
   if(getcwd(pwd_str,256)==NULL)
   {
       printf("mybash>>$");
       fflush(stdout);
       return;
   }
   printf("\033[1;32m%s\033[1;34m@%s%s\033[0m ",pw->pw_name,hostname,pwd_str,s);
   fflush(stdout);
}
//解析命令
char *get_cmd(char *buff,char *myargv[])
{
    if(buff==NULL||myargv==NULL)
    {
        return NULL;
    }
    int i=0;
    char* s=strtok(buff," ");//分隔符是空格
    while(s!=NULL)
    {
        myargv[i++]=s;
        s=strtok(NULL," ");
    }
    return myargv[0];
}
void change_dirent(char *dir)
{
    if(dir==NULL)
    {
        return;
    }
    if(chdir(dir)==-1)
    {
        perror("cd err");
    }
}
int main()
{
    while(1)
    {
        print_info();
        char buff[128]={0};
        fgets(buff,128,stdin);//"ls\n"
        buff[strlen(buff)-1]=0;//"ls"

        char *myargv[MAX_ARG]={0};
        char *cmd=get_cmd(buff,myargv);
        if(cmd==NULL)
        {
            continue;
        }
        else if(strcmp(cmd,"cd")==0)
        {
            change_dirent(myargv[1]);
            continue;
        }
        else if(strcmp(cmd,"exit")==0)
        {
            break;
        }
        else
        {
        	//fork+exec
            pid_t pid=fork();
            if(pid==-1)
            {
                printf("fork err\n");
                continue;
            }
            if(pid==0)
            {
                execvp(cmd,myargv);
                perror("execvp err");
                exit(0);
            }
            wait(NULL);
        }

    }
}

1. 管道

管道可以用来在两个进程之间传递数据,如: ps -ef | grep “bash”, 其中‘|’就是管道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行过滤。

无名在父子进程间,有名在任意进程间
无论有名还是无名,写入管道的数据都在内存中。管道是一种半双工通信方式

  • 有名管道
    有名管道可以在任意两个进程之间通信,默认大小4096
    有名管道的创建:
    ◼ 命令创建: mkfifo FIFO
    ◼ 系统调用创建
    在这里插入图片描述
  • 无名管道
    无名管道主要应用于父子进程间的通信。
    fd[0]:读端
    fd[1]:写端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>

int main()
{
   int fd[2];
   assert(pipe(fd)!=-1);
   pid_t pid=fork();
   assert(pid!=-1);
   if(pid==0)
   {
       close(fd[1]);
       char buff[128]={0};
       read(fd[0],buff,127);
       printf("child read:%s\n",buff);
       close(fd[0]);
   }
   else
   {
       close(fd[0]);
       write(fd[1],"hello",5);
       close(fd[1]);
   }
}

在这里插入图片描述

2. 信号量

信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V操作。

信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。

临界资源:同一时刻,只允许被一个进程或线程访问的资源

临界区:访问临界资源的代码段
在这里插入图片描述

可以看见A和B存在交替出现,即访问冲突,可以用信号量同步解决

  • 信号量的使用

(1)sem.h

#include<sys/sem.h>
#include<unistd.h>

union semun
{
    int val;
};
void sem_init();//创建/已存在的信号量
void sem_p();//加一
void sem_v();//减一
void sem_destroy();//销毁

sem.c

#include "sem.h"

static int semid=-1;

void sem_init()
{
    semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量
    if(semid==-1)//全新创建失败,信号量可能已存在
    {
        semid=semget((key_t)1234,1,0600);
        if(semid==-1)
        {
            perror("semget error");
        }
    }
    else
    {
        union semun a;
        a.val=1;//信号量的初始值
        if(semctl(semid,0,SETVAL,a)==-1)//初始化信号量,用SETVAL将联合体中val的值设置到信号量中
        {
            perror("semctl init error");
        }
    }
}
void sem_p()
{
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=-1;
    buf.sem_flg=SEM_UNDO;
    
    if(semop(semid,&buf,1)==-1)
    {
        perror("p error");
    }
}
void sem_v()
{
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=1;
    buf.sem_flg=SEM_UNDO;
    
    if(semop(semid,&buf,1)==-1)
    {
        perror("v error");
    }
}
void sem_destroy()
{
    if(semctl(semid,0,IPC_RMID)==-1)
    {
        perror("destroy sem error");
    }
}

a.c和b.c
在这里插入图片描述
操作信号量接口介绍:

int semget(key_t key, int nsems, int semflg);
	
	semget()创建或者获取已存在的信号量
	semget()成功返回信号量的 ID, 失败返回-1
	key:两个进程使用相同的 key 值,就可以使用同一个信号量
	nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号量的个数
	semflg 可选: IPC_CREAT IPC_EXCL

int semop(int semid, struct sembuf *sops, unsigned nsops);
	semop()对信号量进行改变,做 P 操作或者 V 操作
	semop()成功返回 0,失败返回-1
	struct sembuf
	{
		unsigned short sem_num; //指定信号量集中的信号量下标
	 	short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
	 	short sem_flg; //SEM_UNDO
	}

int semctl(int semid, int semnum, int cmd, ...);
	semctl()控制信号量
	semctl()成功返回 0,失败返回-1
	semnum:信号量的下标
	cmd 选项: SETVAL IPC_RMID
	union semun
	{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
		struct seminfo *_buf;
	};

ipcs/ipcrm

ipcs可以查看消息队列、共享内存、信号量的使用情况,使用ipcrm可以进行删除操作。
在这里插入图片描述
(2)
在这里插入图片描述
sem.h

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

#define SEM_MAX 3
#define SEM1 0
#define SEM2 1
#define SEM3 2

union semun
{
    int val;
};

void sem_init();
void sem_p(int index);
void sem_v(int index);
void sem_destroy();

sem.c

#include "sem.h"
static int semid=-1;

void sem_init()
{
    int arr[SEM_MAX]={1,0,0};//信号量初始值
    semid=semget((key_t)1234,SEM_MAX,IPC_CREAT|IPC_EXCL|0600);
    if(semid==-1)
    {
        semid=semget((key_t)1234,SEM_MAX,0600);
        if(semid==-1)
        {
            printf("semget err\n");
            return;
        }
    }
    else{
        union semun a;
        int i=0;
        for(;i<SEM_MAX;++i)
        {
            a.val=arr[i];
            if(semctl(semid,i,SETVAL,a)==-1)
            {
                printf("semctl setval err\n");
            }
        }
    }
}
void sem_p(int index)
{
    if(index<0||index>=SEM_MAX)
    {
        printf("index err\n");
        return;
    }
    struct sembuf buf;
    buf.sem_num=index;
    buf.sem_op=-1;
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        printf("semop p err\n");
    }
}
void sem_v(int index)
{
    if(index<0||index>=SEM_MAX)
    {
        printf("index err\n");
        return;
    }
    struct sembuf buf;
    buf.sem_num=index;
    buf.sem_op=1;
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        printf("semop v err\n");
    }
}
void sem_destroy()
{
    if(semctl(semid,0,IPC_RMID)==-1)
    {
        printf("sem destroy err\n");
    }
}

在这里插入图片描述

3. 共享内存

共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问
在这里插入图片描述

int shmget(key_t key, size_t size, int shmflg);
	shmget()用于创建或者获取共享内存
	shmget()成功返回共享内存的 ID, 失败返回-1
	key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
	size: 创建共享内存时,指定要申请的共享内存空间大小
	shmflg: IPC_CREAT IPC_EXCL
void* shmat(int shmid, const void *shmaddr, int shmflg);
 	shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间
 	shmat()成功返回返回共享内存的首地址,失败返回 NULL
 	shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
 	shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
int shmdt(const void *shmaddr);
	shmdt()断开当前进程的 shmaddr 指向的共享内存映射
	shmdt()成功返回 0, 失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
	shmctl()控制共享内存
	shmctl()成功返回 0,失败返回-1
	cmd: IPC_RMID

(1)进程a向共享内存中读入数据,进程b从共享内存中读取数据并显示

a.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/shm.h>

int main()
{
  int shmid=shmget((key_t)1234,256,IPC_CREAT|0600);
  assert(shmid!=-1);
  
  char *s=(char*)shmat(shmid,NULL,0);
  assert(s!=(char*)-1);

  strcpy(s,"hello");
  shmdt(s);
}

b.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/shm.h>

int main()
{
  int shmid=shmget((key_t)1234,256,IPC_CREAT|0600);
  assert(shmid!=-1);

  char *s=(char*)shmat(shmid,NULL,0);
  assert(s!=(char*)-1);

  printf("%s",s);
  shmdt(s);
}

(2)进程 a 从键盘循环获取数据并拷贝到共享内存中,进程 b 从共享内存中获取并打印数据。要求进程 a 输入一次,进程 b 输出一次,进程 a 不输入,进程 b 也不输出。
sem.h

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<malloc.h>
#include<sys/shm.h>
#include<sys/sem.h>


#define SEM_W 0
#define SEM_R 1

typedef union SemUn
{
    int val;
}SemUn;

int SemGet(int key,int semVal[],int nsems);
int SemP(int semid,int index);
int SemV(int semid,int index);
int SemDel(int semid);

sem.c

#include "sem.h"

int SemGet(int key,int semVal[],int nsems)
{
    int semid=semget((key_t)key,nsems,0664);
    if(semid==-1)
    {
        semid=semget((key_t)key,nsems,0664|IPC_CREAT);
        if(semid==-1)
        {
            return -1;
        }
        int i=0;
        for(;i<nsems;i++)
        {
            SemUn un;
            un.val=semVal[i];
            if(semctl(semid,i,SETVAL,un)==-1)
            {
                perror("semctl error");
                return -1;
            }
        }
    }
    return semid;
}
int SemP(int semid,int index)
{
    struct sembuf buf;
    buf.sem_num=index;
    buf.sem_op=-1;
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        return -1;
    }
    return 0;
}
int SemV(int semid,int index)
{
    struct sembuf buf;
    buf.sem_num=index;
    buf.sem_op=1;
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("semop error");
        return -1;
    }
    return 0;
}
int SemDel(int semid)
{
    if(shmctl(semid,0,IPC_RMID)==-1)
    {
        perror("semctl del error");
        return -1;
    }
    return 0;
}

a.c

#include "sem.h"

int main()
{
  int shmid=shmget((key_t)1234,256,IPC_CREAT|0664);
  assert(shmid!=-1);
  
  int semVal[2]={1,0};
  int semid=SemGet(1000,semVal,2);

  char *s=(char*)shmat(shmid,NULL,0);
  assert(s!=(char*)-1);

  while(1)
  {
    char buff[256]={0};
    printf("please input:");
    fgets(buff,256,stdin);
    SemP(semid,SEM_W);
    strcpy(s,buff);
    SemV(semid,SEM_R);
    if(strncmp(s,"end",3)==0)
    {
      break;
    }
  }
  shmdt(s);
  shmctl(shmid,IPC_RMID,NULL);
  exit(0);
}

b.c

#include "sem.h"

int main()
{
  int shmid=shmget((key_t)1234,256,IPC_CREAT|0664);
  assert(shmid!=-1);
  
  int semVal[2]={1,0};
  int semid=SemGet(1000,semVal,2);

  char *s=(char*)shmat(shmid,NULL,0);
  assert(s!=(char*)-1);

  while(1)
  {
    SemP(semid,SEM_R);
    if(strncmp(s,"end",3)==0)
    {
      break;
    }
    printf("lenth=%d:%s",strlen(s)-1,s);
    SemV(semid,SEM_W);
  }
  shmdt(s);
  shmctl(shmid,IPC_RMID,NULL);
  SemDel(semid);
  exit(0);
}

运行结果
在这里插入图片描述

4. 消息队列

使用场景:

  • 将消息队列应用在日志处理当中,比如 Kafka的应用,解决大量日志传输的问题。
  • 秒杀活动一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列;可以控制活动的人数;可以缓解短时间内高流量压垮应用。
  • 异步处理,用户注册后,需要发注册邮件和注册短信。传统的做法有两种,串行和并行方 式。
  • 消息通讯是指,消息队列一般都内置了高效的通信机制,因此可以使用在纯粹的消息通信应用中, 比如实现点对点消息交互,或者聊天室等。
    在这里插入图片描述
int msgget(key_t key, int msqflg);
 	msgget()创建或者获取一个消息队列
 	msgget()成功返回消息队列 ID,失败返回-1
 	msqflg: IPC_CREAT
int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);
	msgsnd()发送一条消息,消息结构为:
	struct msgbuf
	{
	 long mtype; // 消息类型, 必须大于 0
	 char mtext[1]; // 消息数据
	};
	msgsnd()成功返回 0, 失败返回-1
	msqsz: 指定 mtext 中有效数据的长度
	msqflg:一般设置为 0 可以设置 IPC_NOWAIT
ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
 	msgrcv()接收一条消息
 	msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
 	msqtyp: 指定接收的消息类型,类型可以为 0
 	msqflg: 一般设置为 0 可以设置 IPC_NOWAIT
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	msgctl()控制消息队列
	msgctl()成功返回 0,失败返回-1
	cmd: IPC_RMID
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>

struct mess
{
  long type;
  char buff[128];
};

int main()
{
  int msgid=msgget((key_t)1234,IPC_CREAT|0600);
  assert(msgid!=-1);

  struct mess dt;
  dt.type=1;
  strcpy(dt.buff,"hello");
  msgsnd(msgid,&dt,128,0);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>

struct mess
{
  long type;
  char buff[128];
};

int main()
{
  int msgid=msgget((key_t)1234,IPC_CREAT|0600);
  assert(msgid!=-1);

  struct mess dt;
  msgrcv(msgid,&dt,128,1,0);
  printf("read mess:%s\n",dt.buff);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值