Linux中进程通信之信号

信号

信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

关于信号指令的查看:kill -l

例如我们之前使用的kill -9 pid用于杀死一个进程

使用一个死循环

成功发送kill -9指令,杀死该进程

信号通信的框架

        信号的发送(发送信号进程):kill、raise、alarm

        信号的接收(接收信号进程) : pause()、 sleep、 while(1)

        信号的处理(接收信号进程) :signal

相关信号的含义

1.信号的发送(发送信号进程)

kill:

所需头文件:

#include <signal.h>

#include <sys/types.h>

函数原型:int kill(pid_t pid, int sig); 

参数:          

        函数传入值:pid                     

                正数:要接收信号的进程的进程号                     

                0:信号被发送到所有和pid进程在同一个进程组的进程                      

                 ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)           

        sig:信号   

函数返回值:

                成功  0  

                出错  ‐1  

所以我们可以使用一个封装的函数来使用信号命令

也可以发送信号杀死进程

2.raise: 发信号给自己 == kill(getpid(), sig)

所需头文件:     #include <signal.h>

                        #include <sys/types.h>

函数原型:  int  raise(int sig); 

参数:               

        函数传入值:sig:信号   

函数返回值:                

        成功  0  

        出错  ‐1 

我们先写一个简单的例子

成功对自己发送了信号

再来举个例子

父子进程,对子进程发送暂停信号(SIGTSTP)

程序运行8秒之内,父进程处于s+状态(睡眠),子进程处于T+状态(暂停)

8秒之后,父进程处于R+状态(运行)(因为while(1)循环),子进程仍旧处于T+状态

我们对代码进行修改

父进程S+状态,子进程T+状态

父进程R+状态,子进程Z+(变为僵尸进程),因为父进程想要回收,但是子进程被命令杀死,父进程还处于运行态无法进行回收,子进程变为僵尸进程。

我们在父进程执行循环之前加上阻塞函数等待子进程并回收

8秒之前还是父进程S+状态,子进程T+状态

而在8秒之后,父进程开始运行循环,进入R+(运行态),子进程成功被回收

3.alarm : 发送闹钟信号的函数

alarm 与 raise 函数的比较:

相同点:

                让内核发送信号给当前进程

不同点:

                alarm 只会发送SIGALARM信号

                alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号

所需头文件#include   <unistd.h>

函数原型 unsigned int  alarm(unsigned int seconds)   

参数:          

        seconds:指定秒数 

返回值:            

        成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余      时间,否则返回0。     

        出错:‐1 

举个简单例子

信号的接收

接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束 :

sleep

pause:进程状态为S(休眠状态)

函数原型 int  pause(void);   

函数返回值  

成功:0,出错:‐1 

3.信号的处理

收到信号的进程,应该怎样处理? 处理的方式:

1.进程的默认处理方式(内核为用户进程设置的默认处理方式)

A:忽略B:终止进程C: 暂停

2.自己的处理方式: 自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式、

所需头文件  #include <signal.h>   

函数原型  void (*signal(int signum, void (*handler)(int)))(int);   

函数传入值              

 signum:指定信号               

        handler                      

                SIG_IGN:忽略该信号。    //ignore                

                SIG_DFL:采用系统默认方式处理信号                 

                自定义的信号处理函数指针   

函数返回值              

        成功:设置之前的信号处理方式                 

        出错:‐1   

signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函 数;这个函数的返回值是一个函数指针。

上面函数的执行顺序

  1. 程序开始执行
    • 程序进入main函数。
  2. 设置信号处理函数
    • 调用signal(SIGALRM, myfun);,这告诉操作系统当接收到SIGALRM信号时,应该调用myfun函数来处理它。
  3. 打印"before alarm"
    • 执行printf("before alarm\n");,在控制台上输出before alarm
  4. 设置alarm
    • 调用alarm(7);设置一个7秒后的定时器,到时后向进程发送SIGALRM信号。
  5. 打印"after alarm"
    • 立即执行printf("after alarm\n");,在控制台上输出after alarm此时,虽然alarm已经设置,但SIGALRM信号尚未发送,因此myfun函数尚未被调用。
  6. 进入主循环
    • 开始执行while (i < 10)循环。此时,i的初始值为0。
    • 在循环的每次迭代中,i递增,然后调用sleep(1);暂停1秒。
    • 接着,打印当前i的值(例如process 1process 2等)。
  7. SIGALRM信号发送
    • 大约7秒后(从alarm(7);调用开始计时),操作系统向进程发送SIGALRM信号。
    • 由于之前已经通过signal(SIGALRM, myfun);设置了信号处理函数,因此myfun函数被调用。
  8. 执行myfun函数
    • myfun函数内部有一个局部变量i(注意,这与main函数中的i不同),它从0开始,并在循环中递增到9。
    • myfun函数的循环中,每次迭代都会打印出信号编号(SIGALRM的编号,通常为14,但这里使用signum变量表示)和循环计数器的值(局部于myfuni)。
    • 每次打印后,myfun函数中的i递增,并调用sleep(1);暂停1秒。
  9. myfun函数完成
    • myfun函数中的i达到10时,循环结束,myfun函数返回。
  10. 主循环继续
    • myfun函数返回时,主循环(在main函数中)继续执行。由于main函数中的imyfun函数执行期间也在递增,因此主循环将继续执行直到其i也达到10。
  11. 程序结束
    • main函数中的i达到10时,while循环结束,main函数返回0,程序正常结束

使用SIG_IGN函数

使用SIG_DFL函数

信号父子进程之间的通讯

关于下面的函数,几个注意点:

为了避免子进程成为僵尸进程,我们需要对其进行资源回收,但是如果在调用第一个函数signal之后就回收,会导致下面的代码不能按照预期的进行运行

exit()也是一个信号指令,

成功调用signal1函数,目的是使用wait(NULL)进行阻塞,从而达到回收子进程回收资源的目的

子进程回收资源成功

信号灯

信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)

semaphore

函数原型:     

        int semget(key_t key, int nsems, int semflg); 

        //创建一个新的信号量或获取一个已经存在的信号量的键值。 

所需头文件:                

 #include <sys/types.h>              

 #include <sys/ipc.h>                

#include <sys/sem.h>

函数参数:               

         key:和信号灯集关联的key值                

        nsems:  信号灯集中包含的信号灯数目                

        semflg:信号灯集的访问权限 

函数返回值:                 

        成功:信号灯集ID                 

        出错:‐1 

函数原型:

        int semctl ( int semid, int semnum,  int cmd,…union semun arg(不是地址)); 

        //控制信号量,删除信号量或初始化信号量 

所需头文件:                   

 #include <sys/types.h>              

 #include <sys/ipc.h>                

#include <sys/sem.h>

函数参数:                   

        semid:信号灯集ID                   

        semnum: 要修改的信号灯编号                   

        cmd :                            

                GETVAL:获取信号灯的值                           

                SETVAL:设置信号灯的值                           

                IPC_RMID:从系统中删除信号灯集合 

函数返回值:                  

        成功:0                  

        出错:‐1      

删除成功,这里删除的是最后一个生成的信号灯。

PV操作

基本概念

  • 信号量(Semaphore):一个整型变量,用于表示资源的数量。信号量的值大于0时,表示可用资源的数量;值为0时,表示没有可用资源;值为负数时,其绝对值表示等待该资源的进程数。
  • P操作(Wait操作):将信号量的值减1,表示请求分配一个资源。如果操作后信号量的值小于0,则表示没有可用资源,进程将被阻塞进入等待队列。
  • V操作(Signal操作):将信号量的值加1,表示释放一个资源。如果操作前信号量的值小于0,则表示有进程在等待该资源,系统将唤醒等待队列中的一个进程。

int semop(int semid ,struct sembuf *sops ,size_t nsops); 

//用户改变信号量的值。也就是使用资源还是释放资源使用权 

包含头文件:              

        include <sys/sem.h>

参数:        

         semid : 信号量的标识码。也就是semget()的返回值          

        sops是一个指向结构体数组的指针。                      

struct   sembuf

{                                        

        unsigned short  sem_num;//信号灯编号;                                        

        short  sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作           

        short sem_flg;0阻塞,1非阻塞                                     

};         

sem_op : 操作信号灯的个数         

//如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负 数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用 权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

 这里我们进行PV操作实现父子进程之间的通信

对代码进行了修改,如下:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/sem.h>  
#include <sys/ipc.h>  
#include <sys/shm.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <string.h>  
  
// 定义信号量的宏,用于读写操作  
#define SEM_READ 0  
#define SEM_WRITE 1  
  
// semun 联合体用于 semctl 函数的 SETVAL 操作  
union semun {  
    int val;       // 用于设置信号量的值  
    struct semid_ds *buf;  
    unsigned short *array;  
};  
  
// P 操作(等待信号量)  
void Poperation(int index, int semid) {  
    struct sembuf sop;  
    sop.sem_num = index;  // 信号量的索引  
    sop.sem_op = -1;      // P 操作,信号量减 1  
    sop.sem_flg = 0;      // 操作标志,0 表示默认  
    if (semop(semid, &sop, 1) == -1) {  
        perror("semop");  // 如果 semop 失败,打印错误信息  
        exit(1);          // 退出程序  
    }  
}  
  
// V 操作(释放信号量)  
void Voperation(int index, int semid) {  
    struct sembuf sop;  
    sop.sem_num = index;  // 信号量的索引  
    sop.sem_op = 1;       // V 操作,信号量加 1  
    sop.sem_flg = 0;      // 操作标志,0 表示默认  
    if (semop(semid, &sop, 1) == -1) {  
        perror("semop");  // 如果 semop 失败,打印错误信息  
        exit(1);          // 退出程序  
    }  
}  
  
int main() {  
    key_t key;           // 用于生成唯一的标识符  
    int semid, shmid;    // 信号量和共享内存段的标识符  
    char *shmaddr;       // 指向共享内存段的指针  
    pid_t pid;           // 进程标识符  
  
    // 使用 ftok 生成唯一的 key  
    key = ftok("123", 65);  
    if (key == (key_t)-1) {  
        perror("ftok");  
        exit(1);  
    }  
  
    // 创建信号量集  
    semid = semget(key, 2, IPC_CREAT | 0755);  
    if (semid < 0) {  
        perror("semget");  
        return -2;  
    }  
  
    // 初始化信号量  
    union semun myun;  
    myun.val = 0;  
    semctl(semid, SEM_READ, SETVAL, myun);  // 初始化读信号量为 0  
    myun.val = 1;  
    semctl(semid, SEM_WRITE, SETVAL, myun); // 初始化写信号量为 1  
  
    // 创建共享内存段  
    shmid = shmget(key, 1024, IPC_CREAT | 0755);  
    if (shmid < 0) {  
        perror("shmget");  
        return -3;  
    }  
  
    // 创建子进程  
    pid = fork();  
    if (pid == 0) { // 子进程  
        while (1) {  
            // 附加共享内存段  
            shmaddr = (char *)shmat(shmid, NULL, 0);  
            if (shmaddr == (char *)-1) {  
                perror("shmat");  
                exit(1);  
            }  
  
            // 等待读信号量  
            Poperation(SEM_READ, semid);  
  
            // 读取并打印共享内存内容  
            printf("Child: get shared memory is %s\n", shmaddr);  
  
            // 释放写信号量  
            Voperation(SEM_WRITE, semid);  
  
            // 分离共享内存段  
            shmdt(shmaddr);  
  
            // 暂停一秒,避免过快循环  
            sleep(1);  
        }  
    } 
      else if (pid > 0) { // 父进程  
    char input[32];  
    while (1) {  
        // 获取用户输入  
        printf("Parent: please input to shared memory (q to quit): ");  
        if (fgets(input, sizeof(input), stdin) == NULL) {  
            perror("fgets");  
            break; // 如果读取输入失败,则退出循环  
        }  
  
        // 检查是否输入了退出命令  
        if (input[0] == 'q' && input[1] == '\n') {  
            break; // 退出循环  
        }  
  
        // 附加共享内存段  
        shmaddr = (char *)shmat(shmid, NULL, 0);  
        if (shmaddr == (char *)-1) {  
            perror("shmat");  
            exit(1);  
        }  
  
        // 等待写信号量  
        Poperation(SEM_WRITE, semid);  
  
        // 将输入的数据写入共享内存  
        // 注意:这里直接写入可能不安全,因为fgets包含换行符,并且没有检查缓冲区溢出  
        // 这里为了简化,我们直接写入,但在实际应用中应该更谨慎  
        strncpy(shmaddr, input, sizeof(input) - 1); // 减去1以排除可能的换行符  
  
        // 释放读信号量,允许子进程读取  
        Voperation(SEM_READ, semid);  
  
        // 分离共享内存段  
        shmdt(shmaddr);  
  
        // 可以在这里添加其他逻辑,如处理错误或进行其他任务  
    }  
  
    // 父进程退出前,可以清理资源(可选)  
    // 但在这个例子中,由于子进程将无限循环,除非父进程被外部方式终止,  
    // 否则通常不会执行到这里的清理代码  
  
    // 注意:在实际应用中,你可能需要等待子进程结束,或者发送信号来优雅地终止它  
    // 例如,使用 waitpid 或 kill 函数  
} else {  
    // 如果 fork 失败  
    perror("fork");  
    exit(1);  
}  
  
// 清理信号量和共享内存(这部分代码在实际应用中可能需要放在更合适的位置)  
// 例如,在父进程确定子进程已经终止之后  
// 但在这个例子中,由于子进程是无限循环的,所以这里不执行清理  
// ...  
  
return 0;  
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值