linux:进程间的通信 信号通信

信号通信

进程间通信 ===》信号通信
    应用:异步通信。 中断,,
    1~64;32应用编程。

1.信号的五种类型:
    如何响应:
Term   Default action is to terminate the process.

 Ign    Default action is to ignore the signal.        

 Core   Default action is to  terminate  the  process  and  dump  core  (see core(5)).
Stop   Default action is to stop the process.

Cont   Default  action  is  to  continue  the  process  if  it is currently stopped.

kill

 原型:kill      -xx     xxxx
    发送进程  信号    接收进程
    kill -9 1000
    a.out  9 1000


    1、发送端
   

    #include <sys/types.h>
    #include <signal.h>

    int kill(pid_t pid, int sig);

    功能:通过该函数可以给pid进程发送信号为sig的系统信号。
    参数:pid 要接收信号的进程pid
          sig 当前程序要发送的信号编号 《=== kill  -l
    返回值:成功 0
            失败  -1;

    练习:
        编写一个自己的kill程序,尽量模拟kill命令的效果。
 

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    char buf[10]={0};
    fgets(buf,sizeof(buf),stdin);
    pid_t pid = atoi(buf);
    int ret= kill(pid,9);    
    if(-1 == ret)
    {
        perror("kill");
        return 1;
    }
    return 0;
}

int raise(int sig)== kill(getpid(),int sig);
    功能:给进程自己发送sig信号

 alarm

  unsigned int alarm(unsigned int seconds);      //SIGALAM
    功能:定时由系统给当前进程发送信号
               也称为闹钟函数

          闹钟只有一个,定时只有一次有效,
          但是必须根据代码逻辑是否执行判断。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    alarm(5);
    while(1)
    {
        printf("i'm sleep\n");
        sleep(1);
    }
    return 0;
}
  • alarm设置的定时器会在5秒后中断程序,并且sleep调用会被终止。
  • 程序中的while(1)循环是无止境的,但是实际上由于alarm的作用,循环会在5秒后被中断。
  • 程序没有处理SIGALRM信号,所以当定时器到期时,程序会终止。

pause

  int pause(void);
    功能:进程暂停,不再继续执行,除非
               收到其他信号。

#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
    int i = 0 ;
    while(1)
    {
        printf("i'm working\n");
        sleep(1);
        i++;
        if(i== 5)
        {
            pause();
        }
    
    }
    return 0;
}

2、信号  kill  -l  ==>前32个有具体含义的信号

以下是前 32 个有具体含义的信号:

1. SIGHUP:终端挂起或者控制进程终止。

2. SIGINT:中断信号,通常由用户按下 Ctrl+C 产生。

3. SIGQUIT:退出信号,通常由用户按下 Ctrl+\ 产生。

4. SIGILL:非法指令信号。

5. SIGTRAP:跟踪陷阱信号。

6. SIGABRT:异常终止信号,通常由调用 abort 函数产生。

7. SIGBUS:总线错误信号。

8. SIGFPE:浮点异常信号。

9. SIGKILL:立即终止信号,无法被捕获或忽略。

10. SIGUSR1:用户定义信号 1。

11. SIGSEGV:段错误信号。

12. SIGUSR2:用户定义信号 2。

13. SIGPIPE:管道破裂信号。

14. SIGALRM:闹钟信号。

15. SIGTERM:终止信号,可以被进程捕获并进行一些清理操作后再终止。

16. SIGSTKFLT:协处理器栈错误信号。

17. SIGCHLD:子进程状态改变信号。

18. SIGCONT:继续执行被暂停的进程。

19. SIGSTOP:停止信号,无法被捕获或忽略。

20. SIGTSTP:终端停止信号,通常由用户按下 Ctrl+Z 产生。

21. SIGTTIN:后台进程从终端读取数据时收到的信号。

22. SIGTTOU:后台进程向终端写数据时收到的信号。

23. SIGURG:紧急情况信号,通知有紧急数据在套接字上。

24. SIGXCPU:超过 CPU 时间限制信号。

25. SIGXFSZ:超过文件大小限制信号。

26. SIGVTALRM:虚拟定时器信号。

27. SIGPROF:性能分析定时器信号。

28. SIGWINCH:窗口大小改变信号。

29. SIGIO:异步 I/O 信号。

30. SIGPWR:电源故障信号。

31. SIGSYS:系统调用错误信号。

32. SIGRTMIN:实时信号最小值。

3、接收端

       每个进程都会对信号作出默认响应,但不是唯一响应。
        一般如下三种处理方式:
        1、默认处理
        2、忽略处理 9,19
        3、自定义处理 9,19 捕获

以上三种方式的处理需要在如下函数上实现。

   信号注册函数原型:

 void ( *signal(int signum, void (*handler)(int)) ) (int);
     typedef void (*sighandler_t)(int);
     ===》void (*xx)(int); == void fun(int);
     ===》xx是 void fun(int) 类型函数的函数指针
     ===》typedef void(*xx)(int)   sighandler_t; ///错误
          typedef int   myint;

     ===>sighandler_t signal(int signum, sighandler_t handler);
     ===> signal(int sig, sighandler_t fun);
     ===> signal(int sig, xxx fun);
     ===>fun 有三个宏表示:SIG_DFL 表示默认处理
                           SIG_IGN 表示忽略处理
                           fun     表示自定义处理
结论: 9  19 不能被忽略也不能被自定义

自定义信号处理:
    1、必须事先定义自定义函数,必须是如下格式:
        void fun(int sig)  sig 接收到的信息编号
        {

        }

    2、在所有的信号中有如下两个特列:
        10 SIGUSR1
        12 SIGUSR2
        
        专门预留给程序员使用的未定义信号。

练习 SIGUSR1的使用

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

void myhandle1(int num)
{
    if(num == SIGUSR1)
    {
        static int i = 0 ;
        printf("老爸在叫你\n");
        i++;
        if(3==i)
        {
            signal(SIGUSR1,SIG_IGN);
        }
    }
    if(num == SIGUSR2)
    {
        static int j = 0 ;
        printf("老妈在叫你\n");
        j++;
        if(5 == j )
        {
            signal(SIGUSR2,SIG_DFL);
        }
    }
}
int main(int argc, char *argv[])
{

    signal(SIGUSR1,myhandle1);
    signal(SIGUSR2,myhandle1);
    while(1)
    {
        printf("i'm playing pid:%d\n",getpid());
        sleep(1);
    }
    return 0;
}

练习:SIGCHLD的使用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
void handle(int num)
{
    pid_t pid = wait(NULL);
    printf("wait pid :%d\n",pid);
}
int main(int argc, char *argv[])
{
    signal(SIGCHLD,handle);
    pid_t pid = fork();
    if(pid>0)
    {
        while(1)
        {
            printf("father processing...\n");
            sleep(1);
        
        }
    }
    else if(0 == pid) 
    {
        printf("child pid %d\n",getpid());
        sleep(rand()%3);
        printf("child will done %d\n",getpid());
        exit(0);
    }
    else 
    {
    
        perror("fork");
        return 1;
    }

    return 0;
}

内存共享

shm,sem,msg
system v :  共享内存  信号量集

IPC对象操作通用框架:
0x  ftok
key值 ==> 申请 ==》读写 ==》关闭 ==》卸载      (
key值:唯一键值)


key值创建方式有三种:

    1、IPC_PRIVATE 固定的私有键值,

        其值等于 0x0一般用于有亲缘关系的进程间使用。

    2、ftok()创建临时键值。

    #include <sys/types.h>
    #include <sys/ipc.h>
                "/etc"                        '!'
    key_t ftok(const char *pathname, int proj_id);
    功能:通过该函数可以将pathname指定的路径用来以
          proj_id生成唯一的临时键值。
    参数:pathname 路径+名称===》任意文件,只要不会
          被删除重建即可。
          proj_id  整形的数字,一般用ASCII码的单字符
          表示与参数1的运算。

    返回值:成功 返回唯一键值
            失败  -1;

ipcs -a 查询共享内存,信号量集,消息队列
ipcrm -s 删除信号量集
          -m 删除共享内存

             

共享内存 ===》效率最高的进程间通信方式

    操作流程:
     key ==》申请对象 ==》映射对象==》读写对象
         ==》撤销映射 ==》删除对象
 

   1、申请对象:
shmget()
#include <sys/ipc.h>
#include <sys/shm.h>
ps aux|grep a.out
share memory get  IPC_CREAT|0666
int shmget(key_t key, size_t size, int shmflg);

功能:使用唯一键值key向内核提出共享内存使用申请
参数:key  唯一键值
  size  要申请的共享内存大小
  shmflg 申请的共享内存访问权限,八进制表示
  如果是第一个申请,则用IPC_CREAT
  如果要检测是否存在,用IPC_EXCL
返回值:成功 返回共享内存id,一般用shmid表示
失败  -1;

share memory attach

2.映射对象:shmat()
void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:将指定shmid对应的共享内存映射到本地内存。
参数:shmid 要映射的本地内存
  shmaddr 本地可用的地址,如果不确定则用NULL,表示
  由系统自动分配。
  shmflg  
  0,表示读写
  SHM_RDONLY,只读
返回值:成功 返回映射的地址,一般等于shmaddr
失败 (void*)-1

3.读写共享内存:类似堆区内存的直接读写:

char * p ;

write(fd,p,);
read(fd,p,1024);
memcpy(p,buf,1024);strcpy();
memset(p,0,1024);== bzero(p,1024);
memcmp(p,buf,1024); strcmp(a,b);

字符串:  strcpy(p,"hello");
字符/数字: 直接赋值

4.撤销映射:shmdt

int shmdt(const void *shmaddr);
功能:将本地内存与共享内存断开映射关系。
参数:shmaddr 要断开的映射地址。
返回值:成功  0
失败  -1;

5.删除对象:shmctl

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:修改共享内存属性,也可以删除指定的共享内存对象。
参数:shmid 要删除的共享内存对象
  cmd IPC_RMID 删除对象的宏
  buff NULL 表示只删除对象。
返回值:成功 0
失败 -1
以上共享内存可能存在如下问题:
进程1 写入共享内存,如何通知进程2 读共享内存。

练习:使用共享内存完成两个进程间的进程间通信,

写:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
    
    key_t key = ftok("./",'!');
    if(-1 == key)
    {
        perror("ftok");
        return 1;
    }
    printf("key is 0x%x\n",key);
    int shmid = shmget(key,4096,IPC_CREAT|0666);
    if(-1 == shmid)
    {
        perror("shmget ");
        exit(1);
    }

    void* p= shmat(shmid,NULL,!SHM_RDONLY);
    if((void *) -1 == p)
    {
        perror("shmat");
        exit(1);
    }
    char buf[]="hello ,shm test";
    // strcpy();
    memcpy(p,buf,strlen(buf));
    shmdt(p);
    return 0;
}

 读:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
    
    key_t key = ftok("./",'!');
    if(-1 == key)
    {
        perror("ftok");
        return 1;
    }
    printf("key is 0x%x\n",key);
    int shmid = shmget(key,4096,IPC_CREAT|0666);
    if(-1 == shmid)
    {
        perror("shmget ");
        exit(1);
    }

    void* p= shmat(shmid,NULL,!SHM_RDONLY);
    if((void *) -1 == p)
    {
        perror("shmat");
        exit(1);
    }
    
    printf("mem:%s\n",(char*)p);
    shmdt(p);
    //shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

IPC对象之信号量集

为了解决共享内存的临界资源访问。以上共享内存可能存在如下问题:
进程1 写入共享内存,如何通知进程2 读共享内存

操作流程:
key ==> 申请信号量集 ==》init==>PV操作 ===》删除信号量


semaphore

1、申请信号量  semget()

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
功能:通过唯一键值向内核提出信号量申请。
参数:key 唯一键值
  nsems 要申请的信号量个数
  semflg 申请的信号量的访问权限
返回值:semid.
失败 -1;

2、pv操作;semop

p ==>sem_wait() ==>sem = sem-1;
v ==>sem_post() ==>sem = sem+1;

int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:修改指定信号量集中信号量的值。
参数:semid 信号量集id
  sops ==》结构体如下:
  struct sembuf
  {
  unsigned short sem_num;  ///信号量集中信号量的编号,默认以0开始
  short  sem_op;   ///信号量的PV操作,如果改值等于-1则表示p
  等于1 则表示v
  等于0 则表示阻塞

  short   sem_flg;   ///信号量的操作方式 0 表示默认阻塞。
  IPC_NOWAIT and SEM_UNDO.
  };
  nsops 信号量的设置值个数。
返回值:成功 0
失败 -1

通常会将以上函数做如下自定义封装:

int my_sem_wait(int id,int sem)
{
struct sembuf mysem;
mysem.sem_num = sem;
mysem.sem_op  = -1;
mysem.flg   = 0;
 
if(semop(id,&mysem,1) < 0)
return -1;
else
return 0;
}
 
int my_sem_post(int id,int sem)
{
struct sembuf mysem;
mysem.sem_num = sem;
mysem.sem_op  = 1;
mysem.flg   = 0;
 
if(semop(id,&mysem,1) < 0)
return -1;
else
return 0;
}
 

3、信号量的删除
 

int semctl(int semid, int semnum, int cmd, ...);
 功能:根据semid删除指定的信号量集
 参数:semid 要删除的信号量集
   semnum 要删除的信号量集中的信号量的编号
   cmd IPC_RMID 删除对象宏
   ...  可变长参数可以不写
返回值:成功 0
失败 -1;

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值