IO进程线程day9(2023.8.7)

一、Xmind整理:

消息队列的原理:

共享内存的原理:

二、课上练习:

练习1:用信号的方式回收僵尸进程(重点!)

1.子进程退出后,父进程会收到17)SIGCHLD信号

2.父进程中捕获17)SIGCHLD信号,给该信号注册新的处理函数。在该新的处理函数中执行waitpid函数,回收僵尸进程。

3.当在信号A的处理函数内部时,再次触发A信号,会导致信号屏蔽,会造成多个子进程短时间内同时退出,父进程只会处理一个17号信号,导致僵尸进程收不干净的问题

4.解决方式:当成功回收到僵尸进程后,再回收一次。直到没有僵尸进程,结束循环。即判断waitpid(-1, NULL, WNOHANG);的返回值

    ①  =0,有子进程,但是没有僵尸进程

    ②  =-1,没有子进程,也没有僵尸进程

小练1:回收一个子进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <head.h>
void handler(int sig)
{
    printf("触发%d信号\n",sig);
    pid_t wpid =waitpid(-1,NULL,WNOHANG);
    printf("wpid =%d\n",wpid);
    return;
}
int main(int argc, const char *argv[])
{
    pid_t cpid = fork();

    if(cpid > 0)
    {
        //捕获2号SIGINT信号
        if(signal(17,handler) == SIG_ERR)
        {
            perror("signal");
            return -1;
        }
        printf("捕获17号信号成功\n");
        while(1)                                                                              
        {
            printf("this id parent: %d %d\n",getpid(),cpid);
            sleep(1);
        }
    }
    else if(0 == cpid)
    {
        for(int i = 0; i<3; i++)
        {
            printf("this is cpid %d %d\n",getppid(),getpid());
            sleep(1);
        }
        printf("子进程 %d 准备退出\n",getpid());
        exit(0);
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}
小练2:多个子进程短时间内同时退出问题
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <head.h>
#include <sys/wait.h>
int count = 0;
void handler(int sig)
{
    while(1)
    {   //当回收成功后,再收一次,直到回收失败
        //=-1,没有僵尸进程,也没有子进程
        //=0,没有僵尸进程,但是有子进程
        pid_t wpid = waitpid(-1,NULL,WNOHANG);
        if(wpid <= 0)
            break;
        printf("[%d] wpid = %d\n",++count,wpid);
    }
       /*
       也可以这样写
       while(waitpid(-1,NULL,WNOHANG) > 0);
       */                                                           

    return;
}
int main(int argc, const char *argv[])
{
    //捕获17号SIGINT信号
    if(signal(17,handler) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    printf("捕获17号信号成功\n");

    int i = 0;
    while(i < 100)
    {
        if(fork() == 0)   //若是子进程
        {
            exit(0);      //退出
        }
        i++;              //只有父进程执行i++
    }
    //能运行到当前位置,则代表是父进程
    while(1)
        sleep(1);

    return 0;
}

练习2:kill

功能:给指定进程或者进程组发送一个信号
原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid_t pid:指定要发送的进程或者进程组;
pid > 0,  将信号发送给指定的进程,进程号==pid参数;
pid == 0, 将信号发送给当前进程组下的所有进程;
pid == -1, 将信号发送给当前进程权限所能送达的所有进程,除了1号进程;
pid < -1,  将信号发送给指定进程组下的所有进程。进程组ID == -pid参数;
int sig:指定要发送的信号的编号,可以填对应的宏;
sig==0代表没有信号被发送,但是依然会检测对方进程是否存在,或者是否有权限访问。
返回值:
成功,返回0;
失败,返回-1,更新errno;

练习3:alarm

功能:设置一个定时器,当时间到后,给当前进程发送一个14) SIGALRM 信号
原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
unsigned int seconds:设置定时器时间,以秒为单位;
seconds == 0, 则取消定时器;
返回值:
>0, 返回上一个定时器没有走完的时间;
=0, 没有未走完的定时器;
小练: 设置一个3s的定时器
 #include <stdio.h>
 #include <unistd.h>
 #include <signal.h>
 
 void callback(int sig)
 {
     printf("alarm...\n");
     alarm(3);     //设置一个3s的定时器
     return;
 }
 
 int main(int argc, const char *argv[])
 {
     //捕获14号信号
     if(signal(14,callback) == SIG_ERR)              
     {
         perror("signal");
         return -1;
     }
 
     alarm(3);    //设置一个3s的定时器
 
     while(1)
     {
         printf("signal...\n");
         sleep(1);
     }
     return 0;
 }
                                                     

练习4:ftok

功能:该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用。只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
char *pathname:文件的路径以及名字; 该文件必须存在且可访问
int proj_id:传入一个非0参数;
返回值:
成功,返回计算得到的key值;
失败,返回-1,更新errno;
小练: 创建消息队列
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc, const char *argv[])
{
    //创建key值
    key_t key =ftok("/",1);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建消息队列
    int msqid = msgget(key,IPC_CREAT|0664);
    if(msqid < 0)
    {
        perror("msgget");
        return -1;
    }
    printf("msqid = %d\n",msqid);
    system("ipcs -q");               //让代码执行shell指令

    return 0;
}                                                                  

练习5:msgget

功能:通过key值到内核内存中找对应的消息队列,并返回消息队列的id ---> msqid
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:
key_t key:ftok函数返回出来的key值;
int msgflg:
        IPC_CREAT:若消息队列不存在,则创建消息队列。若消息队列存在,则忽略该选项;
        IPC_CREAT|0664:创建的同时指定消息队列的权限。
        IPC_CREAT|IPC_EXCL:若消息队列不存在,则创建消息队列。若消息队列存在,则报错;
返回值:
>=0, 成功返回消息队列的id号  msqid;
=-1, 函数运行失败,更新errno;

练习6:msgsnd

功能:将数据打包后发送到消息队列中
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
int msqid:指定要发送到哪个消息队列中;
void *msgp:指定要发送的消息包的首地址; 通用格式如下:
struct msgbuf {
long mtype;       /* message type, must be > 0 */    消息类型,必须大于0;
char mtext[1];    /* message data */  消息内容,类型根据需求修改,想要发什么类型就填什么类型。
                                      大小与下一个参数msgsz指定的一致
              };   
size_t msgsz:消息内容的大小,以字节为单位。   
int msgflg:
0:阻塞方式发送,当消息队列满了,则当前函数阻塞;
IPC_NOWAIT:非阻塞方式,当消息队列满了,该函数不阻塞,且函数运行失败,errno == EAGAIN.
返回值:
=0, 函数运行成功;
=-1, 函数运行失败,更新errno;
小练: 
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <head.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>
 
 struct msgbuf
 {
     long mtype;
     char mtext[128];
 };
 
 int main(int argc, const char *argv[])
 {
     //创建key值
     key_t key =ftok("/",1);
     if(key < 0)
     {
         perror("ftok");
         return -1;
     }
     printf("key = %#x\n",key);
 
     //创建消息队列
     int msqid = msgget(key,IPC_CREAT|0664);
     if(msqid < 0)
     {
         perror("msgget");
         return -1;
     }
     printf("msqid = %d\n",msqid);
 
     struct msgbuf sndbuf;
     while(1)
     {
         printf("请输入消息类型:");
         scanf("%ld",&sndbuf.mtype);
         getchar();
 
         if(0 == sndbuf.mtype)   //若终端输入0,则跳出循环
             break;
         printf("请输入消息内容:");
         fgets(sndbuf.mtext,sizeof(sndbuf.mtext),stdin);
         sndbuf.mtext[strlen(sndbuf.mtext)-1] = 0;
 
         //向消息队列中发送数据
         if(msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtext),0)< 0)
         {
             perror("msgsnd");
             return -1;
         }
         printf("发送成功\n");
         system("ipcs -q");               //让代码执行shell指令
     }
     return 0;
 }

练习7:msgrcv

功能:从消息队列中读取数据
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数:
int msqid: 指定要从哪个消息队列中读取消息;
void *msgp:指定将读取到的数据存储到什么位置,填对应空间的地址; 以什么形式写入,就以什么形式读取。通用格式如下:
struct msgbuf {
long mtype;       /* message type, must be > 0 */    消息类型,必须大于0;
char mtext[1];    /* message data */  消息内容,类型根据需求修改,想要发什么类型就填什么类型。
                                      大小与下一个参数msgsz指定的一致
              };   
size_t msgsz:指定要读取多少个字节大小的消息内容,以字节为单位。   
long msgtyp:指定要读取的消息类型; 
msgtyp == 0, 读取消息队列中的第一条消息; 先进先出;
msgtyp > 0,  指定消息类型读取,读取消息队列中第一条消息类型为 msgtyp参数指定的消息;
msgflg指定了MSG_EXCEPT,读取消息队列中第一条消息类型 不等于 msgtyp参数指定的消息;
vi -t MSG_EXCEPT;   #define MSG_EXCEPT      020000 
msgtyp < 0,  读取消息队列中第一条最小的,且类型小于等于 msgtyp参数绝对值的消息。                  
int msgflg:
0:阻塞方式,当消息队列中没有消息了,该函数阻塞;
IPC_NOWAIT:非阻塞方式运行,当消息队列中没有消息了,该函数不阻塞,运行失败,errno == ENOMSG;
返回值:
>0, 成功读取到的字节数;
=-1, 函数运行失败,更新errno;
代码示例: 

若消息队列中有消息:

                                   mtype     100      101      99      100      101

                                   mtext       aaa     bbb     ccc     ddd     eee

 i. msgtyp == 0
while(1)                                                                   
    {
        //阻塞方式读取消息队列中第一条消息,先进先出的原则
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, 0);
        
        //非阻塞方式读取消息队列中第一条消息,先进先出的原则
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, IPC_NOWAIT);
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
​
输出顺序:
    100 aaa   101 bbb   99 ccc   100 ddd    101 eee
ii. msgtyp > 0 
while(1)
    {
        //1.阻塞方式读取消息队列中第一条消息类型 == 101 的消息
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, 0);
​
        //2.非阻塞方式读取消息队列中第一条消息 == 101 的消息
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT);
                                                                                  
        //3.非阻塞方式读取消息队列中第一条消息类型 != 101的消息 
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT|020000);
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
注释1,2的现象:
    101 bbb     101 eee
第3个的现象:
    100 aaa    99 ccc   100 ddd    
iii.msgtyp < 0
 while(1)
    {
        //阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, 0);
       
        //非阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, IPC_NOWAIT);
​
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
​
现象:
res=128 : 99 ccc
res=128 : 100 aaa
res=128 : 100 ddd

练习8:msgctl

功能:控制消息队列,常用于删除消息队列
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
int msqid:指定要控制的消息队列的id号;
int cmd:
        IPC_STAT:获取消息队列的属性,属性存储在第三个参数中;
        IPC_SET:设置消息队列属性,属性存储在第三个参数中;
        IPC_RMID:删除消息队列,第三个参数无效,填NULL即可;
返回值:
成功,返回0;
失败,返回-1,更新errno;
//删除消息队列
    if(msgctl(msqid, IPC_RMID, NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    printf("删除消息队列成功\n");

练习9:共享内存函数   ftok

功能:该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用。只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
char *pathname:文件的路径以及名字; 该文件必须存在且可访问
int proj_id:传入一个非0参数;
返回值:
成功,返回计算得到的key值;
失败,返回-1,更新errno;

练习10:shmget

功能:通过key值到内核内存中找对应的共享内存,并返回共享内存的id ---> shmid
原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key_t key:ftok函数返回出来的key值;
size_t size:指定要申请多少个字节的共享内存;
int shmflg:
        IPC_CREAT:若消息队列不存在,则创建共享内存。若共享内存存在,则忽略该选项;
        IPC_CREAT|0664:创建的同时指定共享内存的权限。
        IPC_CREAT|IPC_EXCL:若共享内存不存在,则创建共享内存。若共享内存存在,则报错;
返回值:
>=0, 成功返回共享内存的id号  shmid;
=-1, 函数运行失败,更新errno;

练习11:shmat

功能:将共享内存映射到用户空间中
原型:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
int shmid:指定要映射的共享内存id号 , 
const void *shmaddr:指定共享内存要映射到用户空间的位置,填对应空间的首地址; 例如:(void*)0x10
                     填NULL,代表让操作系统自动映射;
int shmflg :
        0:默认方式映射,进程对共享内存可读可写;
        SHM_RDONLY:只读,进程对共享内存只读;
返回值:
成功,返回共享内存映射到用户空间的首地址;
失败,返回 (void *) -1,更新errno;
注意:获取到的映射空间的首地址的指向不允许修改,若修改后会导致首地址找不到,导致内存泄漏,与堆空间首地址不能改变的概念一致

小练: 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <head.h>
#include <sys/ipc.h>
int main(int argc, const char *argv[])
{
    //创建key值
    key_t key = ftok("./",10);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建共享内存,获得shmid号
    int shmid =shmget(key, 32, IPC_CREAT|0664);
    if(shmid < 0)
    {
        perror("shmget");
        return -1;                                 
    }
    printf("shmid = %d\n",shmid);

    //映射共享内存到用户空间
    void* addr = shmat(shmid,NULL,0);
    if((void*)-1 == addr)
    {
        perror("shmat");
        return -1;
    }
    printf("addr = %p\n",addr);
    system("ipcs -m");

    return 0;
}

练习12:shmdt

功能:将共享内存与进程的用户空间断开映射; 当进程不想操作共享内存的时候,就可以断开映射
原型:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
void *shmaddr:指定要断开映射的用户空间的首地址
返回值:
成功,返回0;
失败,返回-1,更新errno;

练习13:shmctl

功能:控制共享内存,常用于删除共享内存
原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
int shmid:指定要控制的共享内存;
int cmd:
        IPC_STAT:获取共享内存的属性,属性存储在第三个参数中;
        IPC_SET:设置共享内存属性,属性存储在第三个参数中;
        IPC_RMID:删除共享内存,第三个参数无效,填NULL即可;
返回值:
成功,返回0;
失败,返回-1,更新errno;
//删除共享内存
    if(shmctl(shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl");
        return -1;
    }
    printf("删除共享内存成功\n");

三、课后作业:

1.要求用消息队列实现AB进程对话

   A进程先发送一句话给B进程,B进程接收后打印

   B进程再回复一句话给A进程,A进程接收后打印

   重复1.2步骤,当收到quit后,要结束AB进程

   实现随时收发:用多进程 多线程。

A进程:
#include <stdio.h>
#include <string.h>
#include <head.h>

struct msgbuf
{
    long mtype;
    char mtext[128];
};
int main(int argc, const char *argv[])
{
    //创建key值
    key_t key =ftok("./",1);

    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n",key);

    //创建消息队列                                                   
    int msqid = msgget(key,IPC_CREAT|0664);
    if(msqid < 0)
    {
        perror("msgget");
        return -1;
    }
    printf("msqid = %d\n",msqid);

    struct msgbuf sndbuf;
    //创建一个子进程
    pid_t cpid = fork();
    if(cpid > 0)
    {
        while(1)
        {
            sndbuf.mtype = 1;
            scanf("%s",sndbuf.mtext);
            msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtype),0);
            if(strcmp(sndbuf.mtext,"quit") == 0)
                break;
        }
    }
    else if(0 == cpid)
    {
        while(1)
        {
            msgrcv(msqid,&sndbuf,sizeof(sndbuf.mtext),2,0);
            if(strcmp(sndbuf.mtext,"quit") == 0)
                break;
            printf("%s\n",sndbuf.mtext);
        }
        kill(getppid(),2);
    }
    else
    {
        perror("fork");
        return -1;
    }
    return 0;
}
B进程:
#include <stdio.h>
#include <string.h>
#include <head.h>
struct msgbuf
{
    long mtype;
    char mtext[128];
};
int main(int argc, const char *argv[])
{
    key_t key = ftok("./",1);
    if(ftok < 0)
    {
        perror("ftok");
        return -1;                                                       
    }
    printf("key = %#x\n",key);

    int msqid = msgget(key,IPC_CREAT|0664);
    if(msqid < 0)
    {
        perror("msgget");
        return -1;
    }

    struct msgbuf sndbuf;
    pid_t cpid = fork();
    if(cpid > 0)
    {
        while(1)
        {
            sndbuf.mtype = 2;
            scanf("%s",sndbuf.mtext);
            msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtype),0);
            if(strcmp(sndbuf.mtext,"quit") == 0)
                break;
        }
    }

    else if(0 == cpid)
    {
        while(1)
        {
            msgrcv(msqid,&sndbuf,sizeof(sndbuf.mtext),1,0);
            if(strcmp(sndbuf.mtext,"quit") == 0)
                break;
            printf("%s\n",sndbuf.mtext);
        }
        kill(getppid(),2);
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

2.要求在共享内存中存入字符串 “1234567”。A进程循环打印字符串,B进程循环倒置字符串,要求结果不允许出现乱序:

提示:共享内存中存储 flag + string.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <sys/shm.h>
int main(int argc, const char *argv[])
{
    //创建key值
    key_t key = ftok("./", 3);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    printf("key = %#x\n", key);

    //通过key值获取shmid号
    int shmid = shmget(key, 32, IPC_CREAT|0777);
    if(shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n", shmid);

    //将共享内存映射到用户空间
    void* addr = shmat(shmid, NULL, 0);
    if((void*)-1 == addr)
    {
        perror("shmat");
        return -1;
    }                                                                            
    printf("addr = %p\n", addr);

    //存储一个str
    *(int *)addr = 0;
    char *str = (char *)addr+4;
    //存储一个字符串
    strcpy(str,"1234567");
    pid_t cpid = fork();
    if(cpid > 0)
    {
        while(1)
        {
            if(*(int *)addr == 0)
            {
                printf("%s\n",(char *)addr+4);
                *(int *)addr = 1;
            }
        }
    }
    else if(0 == cpid)
    {
        while(1)
        {
            if(*(int*)addr == 1)
            {
                char *star = (char *)addr+4;
                char *end  = (char*)addr+4+strlen(str)-1;
                while(star<end)
                {
                    char temp = *star;
                    *star = *end;
                    *end  = temp;
                    star++;
                    end--;
                }
                *(int*)addr = 0;
            }
        }
    }
    else
    {
        perror("fork");
        return -1;
    }

    system("ipcs -m");

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值