2024.12.18(进程间通信)

Linux系统提供了多种进程之间的通信方式

例如:无名管道,有名管道,信号等

1、如果是同一进程下的多个线程,共享资源,所以线程之间的通信只需要注意互斥和同步即可。

2、如果是多个进程。由于进程之间不共享资源,所以进程间的通信需要用到系统提供的内核空间。

 1、无名管道

1、本质也是一个内核空间的文件,存储在内存中,读写数据都是一次性的。

2、无名管道写入数据后,一旦读出数据,数据就消失了。

3、无名管道,一旦打开,就出现读端和写端,如果进行读取数据,就要先关闭写端,如果想要写数据,就要先关闭读端。

4、无名管道工作原理是半双工,也就是同一时刻只能是A写B读,或者A读B写

5、无名管道只能进行亲缘进程间的通信

6、管道文件相关的函数属于文件IO部分,只能使用文件IO读写

7、管道的读写端属于文件描述符,也遵循最小未分配原则。

         单工:只能是A向B发信息,B不能向A发信息。

        半双工:同一时刻只能A向B发信息,或者B向A发信息。

        全双工:同一时刻AB都是收发信息。

无名管道API:

        #include <unistd.h>

                int pipe(int pipefd[2]);

        功能:创建一个无名管道.        pipefd[0]读端         pipefd[1]:写端

        参数:文件描述符数组

        返回值:成功返回0,失败返回-1,并置位错误码。

eg: int pipefd[2];

        if(pipe(pipefd)==-1)

        { perror("pipe");

        return -1; }

练习:父进程写入管道,子进程读取管道数据。

#include <myhead.h>

int main(int argc, const char *argv[])
{
    int pipefd[2];
    char buff[1024] = "hello world";
    char s[1024];
    if(pipe(pipefd)==-1)
    {
        perror("pipe");
        return -1;
    }//读端pipefd[0] 写端pipefd[1]
    pid_t pid = fork();//创建子进程
    if(pid==0)
    {
        close(pipefd[1]);//先关闭写端
        while(1)
        {
            sleep(1);
            read(pipefd[0],s,sizeof(s));
            printf("儿子在读取:%s\n",s);//输出读取的数据
        }
        close(pipefd[0]);//关闭读端
    }
    else if(pid>0)
    {
        close(pipefd[0]);//先关闭读端
        while(1)
        {
            sleep(1);
            write(pipefd[1],buff,sizeof(buff));
        }
        close(pipefd[1]);//完成后关闭写端
    }
    else
    {
        perror("fork");
        return -1;
    }
    
    return 0;
}

验证无名管道大小:

#include <myhead.h>

int main(int argc, const char *argv[])
{
    int pipefd[2];
    if(pipe(pipefd)==-1)
    {
        perror("pipe");
        return -1;
    }

    long int a = 0;
    long int k = 0;
    while(1)
    {
        write(pipefd[1],&a,sizeof(long));
        k+=8;
        printf("%ld\n",k);//65536  2^16K
    }
    close(pipefd[1]);
    return 0;
}

1、当读端存在时,有多少就写多少,写够2^16K为止

2、当写端存在时,有多少就读多少,读完为止会在read处阻塞。

3、当读端不存在,写入管道,会导致管道破裂。

4、当写端不存在时,有多少就读多少,读完为止不会在read处阻塞。

2、有名管道

1、有名字的管道,相对于无名管道,可以进程非亲缘进程间的通信。

2、有名管道写入一次读取一次。

        #include <sys/types.h>

        #include <sys/stat.h>

                int mkfifo(const char *pathname, mode_t mode);

        功能:创建一个有名管道用于非亲缘进程间的通信

        参数1:有名管道名

        参数2:创建时的权限。

        返回值:成功返回0,失败返回-1,并置位错误码。

创建有名管道实现非亲缘进程间的通信:

写端:
#include <myhead.h>

int main(int argc, const char *argv[])
{
    int k = mkfifo("./myfifo",0664);//创建有名管道
    if(k==-1)
    {
        perror("mkfifo");
        return -1;
    }
    
    int fd1 = open("./myfifo",O_WRONLY);//打开管道
    if(fd1==-1)
    {
        perror("open");
        return -1;
    }

    char buff[1024];
    while(1)//循环写入数据
    {
        printf("请输入内容:");
        int res = read(0,buff,sizeof(buff));//输入从0号描述符读取数据
        write(fd1,buff,res);//写入有名管道
    }
    close(fd1);//关闭有名管道
    return 0;
}
读端:
#include <myhead.h>

int main(int argc, const char *argv[])
{
    int fd2 = open("./myfifo",O_RDONLY);//打开管道文件
    if(fd2==-1)
    {
        perror("open");
        return -1;
    }
    char buff[1024];
    while(1)//循环读取数据
    {
        int res = read(fd2,buff,sizeof(buff));
        if(res==0)
        {
            printf("写入端退出\n");
            break;
        }
        write(1,buff,res);//写入标准输出描述符
    }
    close(fd2);//关闭管道文件    
    return 0;
}

练习:创建2个子父进程,父进程写入管道1,子进程读取管道2,

                                          父进程写入管道2,子进程读取管道1,

           实现全双工通信。

1、先创建2个管道文件。

2、创建2个子父进程。

#include <myhead.h>

int main(int argc, const char *argv[])
{
    int fd1 = open("./myfo1",O_WRONLY);
    int fd2 = open("./myfo2",O_RDONLY);
    if(fd1==-1||fd2==-1)
    {
        perror("open");
        return -1;
    }

    char buff[1024];

    pid_t pid = fork();
    if(pid>0)//父进程写入管道1
    {
        while(1)
        {
            printf("请输入内容:\n");
            int res = read(0,buff,sizeof(buff));
            write(fd1,buff,res);//写入管道1
        }
    }
    else if(pid==0)//子进程读取管道2
    {
        while(1)
        {
            int res = read(fd2,buff,sizeof(buff));
            write(1,buff,res);//读取内容显示出来
        }
    }
    else
    {
        perror("fork");
        return -1;
    }
    
    return 0;
}




#include <myhead.h>

int main(int argc, const char *argv[])
{
    int fd1 = open("./myfo1",O_RDONLY);
    int fd2 = open("./myfo2",O_WRONLY);
    if(fd1==-1||fd2==-1)
    {
        perror("open");
        return -1;
    }

    char buff[1024];

    pid_t pid = fork();
    if(pid>0)//父进程写入管道2
    {
        while(1)
        {
            printf("请输入内容:\n");
            int res = read(0,buff,sizeof(buff));
            write(fd2,buff,res);//写入管道1
        }
    }
    else if(pid==0)//子进程读取管道1
    {
        while(1)
        {
            int res = read(fd1,buff,sizeof(buff));
            write(1,buff,res);//读取内容显示出来
        }
    }
    else
    {
        perror("fork");
        return -1;
    }
    
    return 0;
}

3、信号

1、进程间除了管道之外还可以使用信号通信。

2、中断是硬件的操作,信号是模拟硬件的中断。

3、信号可以由内核发送给进程,可以由用户发送给进程,还可以由进程发送给进程。

4、信号的发送接收属于异步通信,也就是各个进程之间互不影响。

5、信号的处理方式三种:默认,忽略,捕获(由用户自定义处理方式)。

6、9) SIGKILL,SIGSTOP既不能忽略也不能捕获,只能执行。

信号种类:

1) SIGHUP                  2) SIGINT                 3) SIGQUIT                4) SIGILL              

5) SIGTRAP 6) SIGABRT                 7) SIGBUS                8) SIGFPE                9) SIGKILL  10) SIGUSR1 11) SIGSEGV                 12) SIGUSR2                13) SIGPIPE              

14) SIGALRM               15) SIGTERM                     16) SIGSTKFLT                17) SIGCHLD

18) SIGCONT         19) SIGSTOP                20) SIGTSTP                 21) SIGTTIN  

22) SIGTTOU                 23) SIGURG                24) SIGXCPU                

25) SIGXFSZ                26) SIGVTALRM                27) SIGPROF                28) SIGWINCH  29) SIGIO                30) SIGPWR                  31) SIGSYS                 34)RTMIN   SIG

35) SIGRTMIN+1                 36) SIGRTMIN+2 37) SIGRTMIN+3              

38) SIGRTMIN+4                39) SIGRTMIN+5                                       40) SIGRTMIN+6       41) SIGRTMIN+7                 42) SIGRTMIN+8                                              

43) SIGRTMIN+9                44) SIGRTMIN+10                45) SIGRTMIN+11                           46) SIGRTMIN+12                47) SIGRTMIN+13                48) SIGRTMIN+14

49) SIGRTMIN+15                50) SIGRTMAX-14                51) SIGRTMAX-13                       52) SIGRTMAX-12                 53)SIGRTMAX-11                 54) SIGRTMAX-10      

55) SIGRTMAX-9                 56) SIGRTMAX-8                 57) SIGRTMAX-7                         58) SIGRTMAX-6                 59) SIGRTMAX-5                          

60) SIGRTMAX-4                 61) SIGRTMAX-3                 62) SIGRTMAX-2                

63) SIGRTMAX-1                                 64) SIGRTMAX 1)

SIGHUP:终端要关闭了,终端会向该终端下运行的所有进程发送一个信号,我要关闭了你们都退了吧。

2) SIGINT:按下ctrl +c键,终止进程。

3) SIGQUIT:按下ctrl +\键,终止进程,会生成一个core dump文件,记录终止进程的状态。 9) SIGKILL:杀死进程,包括后台运行的进程。

10) SIGUSR1,12) SIGUSR2:用户自定义的信号。

11) SIGSEGV:由内核向用户空间发送的段错误信号。

14) SIGALRM:由系统向调用进程发送的定时器信号。

17) SIGCHLD:子进程退出时发送给父进程的信号,告诉父进程他要死了。

18) SIGCONT:将暂停的进程继续运行信号。

19) SIGSTOP:内核发送给进程的暂停信号。

20) SIGTSTP:用户按下了ctrl+z键,暂停进程。

1、信号发送函数signal

        #include <signal.h>

                typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);

        功能:信号处理函数,忽略,默认和捕获操作。

        参数1:信号号

        参数2:   SIG_IGN:忽略信号,不执行任何的操作。

                        SIG_DFL:默认信号,执行信号原始的操作。

                        自定义函数:捕获,某个信号,捕获的信号处理方式由程序员决定。

        返回值:成功返回前一个信号号,失败返回SIG_ERR, 并置位错误码。

2、尝试 忽略,默认,捕获(2) SIGINT也就是ctrl+c

#include <myhead.h>

void hander(int tmy)
{
    if(tmy==SIGINT)
    {
        printf("捕获了ctrl+c\n");
    }
}
int main(int argc, const char *argv[])
{
#if 0
    if(signal(SIGINT,SIG_IGN)==SIG_ERR)//忽略ctrl +c信号
    {
        perror("signal");
        return -1;
    }
    if(signal(SIGINT,SIG_DFL)==SIG_ERR)//默认ctrl +c信号
    {
        perror("signal");
        return -1;
    }
#endif
    if(signal(SIGINT,hander)==SIG_ERR)//hander将会捕获SIGINT信号作为自己的参数
    {
        perror("signal");
        return -1;
    }
        
    int k = 0;
    while(1)
    {
        sleep(1);
        printf("唐明宇打呼噜k = %d\n",k);
        k++;
    }
    return 0;
}

3、尝试 捕获(17) SIGCHLD

#include <myhead.h>
//SIGCHLD
void hander(int tmy)
{
    if(tmy==SIGCHLD)
    {
        printf("捕获到了SIGCHLD\n");
    }
}
int main(int argc, const char *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid>0)
    {
        if(signal(SIGCHLD,hander)==SIG_ERR)//捕获SIGCHLD信号
        {
            perror("signal");
            return -1;
        }
    }
    else if(pid==0)
    {
        sleep(1);
        exit(0);//成功退出子进程
    }
    else
    {
        perror("fork");
        return -1;
    }
    wait(NULL);//阻塞回收子进程资源
    return 0;
}

4、尝试捕获SIGSEGV信号

#include <myhead.h>
void fun(int tmy)
{
    sleep(1);
    if(SIGSEGV==tmy)
    {
        printf("内核发送了段错误信号\n");
    }
}
int main(int argc, const char *argv[])
{
    if(signal(SIGSEGV,fun)==SIG_ERR)//绑定信号
    {
        perror("signal");
        return -1;
    }

    int *p = NULL;
    *p = *p+1;
    while(1);
    return 0;
}

4、捕获SIGTSTP信号 。

#include <myhead.h>

void hander(int tmy)
{
    if(tmy==SIGTSTP)
    {
        printf("捕获到了SIGTSTP\n");
    }
}
int main(int argc, const char *argv[])
{
        if(signal(SIGTSTP,hander)==SIG_ERR)//捕获SIGCHLD信号
        {
            perror("signal");
            return -1;
        }
    while(1)
    {
        printf("hello\n");
        sleep(1);
    }
    return 0;
}

5、SIGALRM信号使用,模拟下棋alarm函数

        #include <unistd.h>

                unsigned int alarm(unsigned int seconds);

        功能:设置定时时间,时间到了内核会向调用进程发送SIGALRM信号 如果第二次设置了定时时间,那么alarm函数返回的是上一次剩余的秒数。

        参数:设置的秒数。

        返回值:成功返回上一次预定的剩余秒数,如果没有上一次预定的秒数返回0

使用规则:

#include <myhead.h>

void hander(int sss)
{
    if(sss==SIGALRM)
    {
        printf("收到内核发送的SIGALRM\n");
    }
}
int main(int argc, const char *argv[])
{
    if(signal(SIGALRM,hander)==SIG_ERR)//绑定SIGALRM信号
    {
        perror("signal");
        return -1;
    }
    unsigned int k = alarm(3);//设置3秒
    printf("k = %d\n",k);
    
    sleep(4);//睡1秒

    unsigned int m = alarm(5);//再次设定时间为5秒
    printf("m = %d\n",m);
    while(1);    
    return 0;
}

实现斗地主:

#include <myhead.h>
void hander(int sss)
{
    if(sss==SIGALRM)//捕获到信号
    {
        printf("系统为您自动出牌一张\n");
    }
    alarm(5);//重新设置定时时间
}
int main(int argc, const char *argv[])
{
    if(signal(SIGALRM,hander)==SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    while(1)
    {
        alarm(5);//设定5秒的定时时间
        printf("请输入你要出的牌:");//输入出的牌
        char ch = fgetc(stdin);
        getchar();
        printf("您出的牌:%c\n",ch);
    }
    return 0;
}

6、信号发送函数:kill raise

验证发送信息函数:

        #include <sys/types.h>

        #include <signal.h>

                int kill(pid_t pid, int sig);

        功能:发送信号给调用进程的函数,可以给自己发送信号也可以给其他进程发送信号

        参数1:进程号 >0:向指定的进程号发送信号

                =0:向调用进程所在的进程组中每一个进程发送信号。

                =-1:向任意的进程发送信号

                <-1:向进程组ID为pid绝对值的所有进程发送信号。

        参数2:发送的信号号

        返回值:成功返回0,失败返回-1,并置位错误码。

        #include <signal.h>

                int raise(int sig);

        功能:调用进程向自己发送一个信号

        参数:信号号

        返回值:成功返回0,失败返回非0.

原理:子进程向父进程发送一个信号(这个信号是自定义信号),父进程收到信号后,调用raise函数自杀。

#include <myhead.h>
void hander(int sss)
{
    printf("逆子啊,我要死了\n");
    raise(SIGKILL);//自杀
}
int main(int argc, const char *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid>0)
    {
        if(signal(SIGUSR1,hander)==SIG_ERR)//父进程绑定了自定义信号
        {
            perror("signal");
            return -1;
        }
    }
    else if(pid==0)
    {
        kill(getppid(),SIGUSR1);//子进程向父进程发送自定义信号
    }
    else
    {
        perror("fork");
        return -1;
    }
    while(1);
    return 0;
}

作业:1 无名管道和有名管道实现一遍

           2 进程间通信信号的默认,忽略,捕获实现一遍。

system V提供了多种通信方式

例如:消息队列,共享内存,信号灯集等IPC对象

1、消息队列

概念:将数据放入内核空间的消息队列中,多个进程之间共享内核空间,所以多个进程都可以访问消息队列数据。

1、消息队列数据读取和写入都是一次性的,读取完就会消失。

2、原理图。

进程A可以放入队列不同类型的消息,进程BC可以选择性的读取队列中的消息,

例如:进程B可以设置只读取消息类型为2的消息,那么其他消息都会被进程B忽略。

当然可以设置某个进程全部读取所有消息。

队列,共享内存,信号IPC对象的获取,都必须先生成密钥。

shell指令:

查看IPC对象:ipcs

查看某个IPC对象:ipcs -q -m -s

删除IPC对象:ipcrm -q 队列号 -m 信号号 -s 共享内存号

2、API函数

        #include <sys/types.h>

        #include <sys/ipc.h>

                key_t ftok(const char *pathname, int proj_id);

        功能:使用参数1的低8位和参数2的低16位生成一个32位的密钥

        参数1:文件名(必须依据存在的文件)

        参数2:整形或者单字符数据。

        返回值:成功返回生成的密钥,失败返回-1,并置位错误码。

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

        #include <sys/msg.h> int msgget(key_t key, int msgflg);

        功能:创建消息队列,返回消息队列的ID

        参数1:密钥

        参数2:创建时的权限      IPC_CREAT:创建

                                                 IPC_EXCL:存在就报错 以位或的方式组合

        返回值:成功返回消息队列的ID,失败返回-1,并置位错误码。

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

                int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

        功能:向消息队列中发送消息

        参数1:消息队列的ID

        参数2:消息类型和消息正文是一个结构体如下:

                struct msgbuf { long mtype; 消息类型(频道)

char mtext[1]; 消息的正文 };

        参数3:消息大小,是正文大小,不是结构体大小。         

        参数4:   0:阻塞

                        IPC_NOWAIT:非阻塞

        返回值:失败返回-1,并置位错误码,成功返回0.

                ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

        功能: 接收消息队列内的消息

        参数1:消息队列的ID

        参数2:接收的消息内容

        参数3:消息大小,是正文大小,不是结构体大小。

        参数4:消息类型

                =0:读取第一条消息不论类型

                >0:读取消息类型为msgtyp的第一条消息。

                <0:读取消息类型小于msgtyp绝对值的第一条消息。

                消息类型:9 6 10 3 2 10 7 9 3

                msgtyp==-5 读取小于5的第一个消息是 3

        参数4: 0:阻塞

                        IPC_NOWAIT:非阻塞

        返回值: 失败返回-1,并置位错误码,成功返回接受的字节数。

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

                int msgctl(int msqid, int cmd, struct msqid_ds *buf);

        功能:消息队列操作函数

        参数1:消息队列ID

        参数2:操作指令

                         IPC_STAT:获取消息队列属性

                         IPC_SET:设置消息队列属性

                         IPC_RMID :删除消息队列

        参数3: struct msqid_ds {

                        struct ipc_perm msg_perm;                所有者和权限

                        time_t msg_stime;                                 最后一次发送的时间

                        time_t msg_rtime;                                 最后一次接收的时间

                        time_t msg_ctime;                                 最后一次修改的时间

                        unsigned long __msg_cbytes;           当前队列所占字节数

                        msgqnum_t msg_qnum;                    当前队列消息个数

                        msglen_t msg_qbytes;                       当前队列最大的消息

                        pid_t msg_lspid;                                 最后一次发送消息的进程ID

                        pid_t msg_lrpid;                                 最后一次接收消息的进程ID };

        参数3的第一个成员如下:

        struct ipc_perm { key_t __key; key值

                        uid_t uid; 所有者的用户ID

                        gid_t gid; 所有者的组ID

                        uid_t cuid; 创建者的用户ID

                        gid_t cgid; 创建者的组ID

                        unsigned short mode; 权限

                        unsigned short __seq; 序列号 };

        返回值:成功返回0,失败返回-1,并置位错误码。

实现两个进程间的通信。

发送端:
#include <myhead.h>
//1、创建key值
//2、创建消息队列
//3、发送消息
struct msgbuf
{
    long mtype;//消息队列的类型
    char mtext[1024];//消息队列的正文
};
#define len sizeof(struct msgbuf)-sizeof(long)


int main(int argc, const char *argv[])
{
    struct msgbuf msg;

    key_t key = ftok("./1.txt",'B');//1、创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int msgid = msgget(key,IPC_CREAT|0664);//2、创建消息队列
    if(msgid==-1)
    {
        perror("msgget");
        return -1;
    }
    
    printf("key值:%d,队列ID:%d\n",key,msgid);
    while(1)
    {
        printf("请输入要发送的消息类型:");
        scanf("%ld",&msg.mtype);
        getchar();
        printf("请输入要发送的消息正文:");
        fgets(msg.mtext,sizeof(msg.mtext),stdin);
        msg.mtext[strlen(msg.mtext)-1] = '\0';
        if(msgsnd(msgid,&msg,len,0)==-1)//阻塞发送
        {
            perror("msgsnd");
            return -1;
        }
        if(strcmp("quit",msg.mtext)==0)
        {
            break;
        }
        printf("发送成功\n");
    }

    struct msqid_ds sss;

    if(msgctl(msgid,IPC_STAT,&sss)==-1)//获取属性
    {
        perror("msgctl");
        return -1;
    }
    printf("用户ID:%d\n",sss.msg_perm.uid);//输出所有者ID

    if(msgctl(msgid,IPC_RMID,NULL)==-1)//删除消息队列
    {
        perror("msgctl");
        return -1;
    }
    
    
    return 0;
}


接收端:
#include <myhead.h>
//1、创建key值
//2、创建消息队列
//3、接收消息
struct msgbuf
{
    long mtype;//消息队列的类型
    char mtext[1024];//消息队列的正文
};
#define len sizeof(struct msgbuf)-sizeof(long)

int main(int argc, const char *argv[])
{
    struct msgbuf msg;

    key_t key = ftok("./1.txt",'B');//1、创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int msgid = msgget(key,IPC_CREAT|0664);//2、创建消息队列
    if(msgid==-1)
    {
        perror("msgget");
        return -1;
    }
    while(1)
    {
        if(msgrcv(msgid,&msg,len,0,0)==-1)//接收消息
        {
            perror("msgrcv");
            return -1;
        }
        printf("%s\n",msg.mtext);
    }
    
    return 0;
}

2、共享内存

概念

1、数据写入到共享内存后会一直存在,除非删除共享内存段。

2、共享内存是由物理内存通过内存映射而产生的一段虚拟内存,所有进程共享这段虚拟内存,都可以读写这段虚拟内存的内容。

1、原理图

物理内存通过内存的映射,在内核生成了共享的虚拟内存。

而虚拟内存特点:

1、写入的数据会一直存在,可以反复读取。

2、第二次写入数据后,第一次写入的数就被覆盖了。

3、一次只能写入一种(一个)数据。

4、进程断开共享内存的读写,共享内存还在,只是进程不能读写。

5、物理映射断开后,会导致共享内存直接消失。

2、API函数

        #include <sys/types.h>

        #include <sys/ipc.h>

                key_t ftok(const char *pathname, int proj_id);

        功能:使用参数1的低8位和参数2的低16位生成一个32位的密钥

        参数1:文件名(必须依据存在的文件)

        参数2:整形或者单字符数据。

        返回值:成功返回生成的密钥,失败返回-1,并置位错误码。

        #include <sys/ipc.h>

        #include <sys/shm.h>

                int shmget(key_t key, size_t size, int shmflg);

        功能:获取共享内存段

        参数1:key值

        参数2:共享内存的大小

        参数3: IPC_CREAT:创建

                      IPC_EXCL :存在就报错

        返回值:成功返回共享内存段的ID,失败返回-1,并置位错误码。

        #include <sys/types.h>

        #include <sys/shm.h>

                void *shmat(int shmid, const void *shmaddr, int shmflg);

        功能:调用进程与共享内存映射起来,进程可以访问共享内存。

        参数1:共享内存的ID

        参数2: NULL:默认系统自动选择内存的对齐页 SHM_RND:手动选择对齐。

        参数3:    SHM_EXEC:共享内存存在就会报错

                         SHM_RDONLY:共享内存只读权限。

                         0:读写权限

        返回值:成功返回共享内存的映射地址,失败返回(void *)-1,并置位错误码。

                int shmdt(const void *shmaddr);

        功能:取消调用进程与共享内存的映射,进程无法访问共享内存。

        参数: NULL:默认系统自动选择内存的对齐页

                    SHM_RND:手动选择对齐。

        返回值:成功返回共享内存的映射地址,失败返回-1,并置位错误码。

        #include <sys/ipc.h>

#include <sys/shm.h>

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

功能:操作共享内存段

参数1:共享内存段ID

参数2: IPC_STAT:获取共享内存段的属性

IPC_SET:设置共享内存段的属性

IPC_RMID:删除共享内存段。

返回值:成功返回0,失败返回-1,并置位错误码。

参数3: struct shmid_ds

                { struct ipc_perm shm_perm;         /* Ownership and permissions */

                size_t shm_segsz;         /* Size of segment (bytes) */

                time_t shm_atime;         /* Last attach time */

                time_t shm_dtime;         /* Last detach time */

                time_t shm_ctime;         /* Last change time */

                pid_t shm_cpid;         /* PID of creator */

                pid_t shm_lpid;         /* PID of last shmat(2)/shmdt(2) */

                shmatt_t shm_nattch;         /* No. of current attaches */

                ... };

                struct ipc_perm {

                key_t __key;         /* Key supplied to shmget(2) */

                uid_t uid;                 /* Effective UID of owner */

                gid_t gid;         /* Effective GID of owner */         

                uid_t cuid;         /* Effective UID of creator */

                gid_t cgid;         /* Effective GID of creator */

                unsigned short mode; /* Permissions + SHM_DEST and SHM_LOCKED flags */

                unsigned short __seq;         /* Sequence number */ };

        返回值:成功返回0,失败返回-1,并置位错误码。

两个进程使用共享内存通信

发送
#include <myhead.h>
//1、创建key值
//2、获取共享内存段
//3、调用进程与共享内存映射
//4、发送消息
#define BACKPAGE  4096
int main(int argc, const char *argv[])
{
    key_t key = ftok("./",0);//创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,BACKPAGE,IPC_CREAT|0664);//获取共享内存段
    if(shmid==-1)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n",shmid);    
    char *p = shmat(shmid,NULL,0);//NULL:默认对齐 0:读写权限
    if(p==(void *)-1)
    {
        perror("shmat");
        return -1;
    }
    char buff[1024];
    while(1)
    {
        printf("亲输入消息:");
        fgets(buff,sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\0';//最后字符改为\0
        strcpy(p,buff);//写入内存
        if(strcmp("quit",buff)==0)
        {
            break;
        }
    }

    shmdt(NULL);//断开进程和共享内存的映射

    struct shmid_ds sss;

    shmctl(shmid,IPC_STAT,&sss);//获取共享内存段属性
    printf("用户ID%d\n",sss.shm_perm.uid);//输出用户ID
    int k = shmctl(shmid,IPC_RMID,NULL);//删除共享内存段
    if(k==-1)
    {
        perror("shmctl");
        return -1;
    }
    printf("删除成功\n");
    return 0;
}


接收
#include <myhead.h>
//1、创建key值
//2、获取共享内存段
//3、调用进程与共享内存映射
//4、接收消息
#define BACKPAGE  4096
int main(int argc, const char *argv[])
{
    key_t key = ftok("./",0);//创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,BACKPAGE,IPC_CREAT|0664);//获取共享内存段
    if(shmid==-1)
    {
        perror("shmget");
        return -1;
    }

    char *p = shmat(shmid,NULL,0);//NULL:默认对齐 0:读写权限
    if(p==(void *)-1)
    {
        perror("shmat");
        return -1;
    }
    char buff[1024];
    while(1)
    {
        printf("收到消息:%s\n",p);
        if(strcmp("quit",p)==0)
        {
            break;
        }
    }

3、信号灯集

概念:类似于线程同步的无名信号量,只不过是用在进程和进程之间的同步互斥。

A进程进入内核时,B进程只能处于等待状态,A进程执行结束B进程才能进入内核空间。

本质上也是维护了一个value值,当申请资源时,value是1,表示可以申请资源。是0表示无法申请资源。

当然信号灯集并不是一个灯,是一组灯。

1、原理图

2、API函数

        #include <sys/types.h>

        #include <sys/ipc.h>

                key_t ftok(const char *pathname, int proj_id);

        功能:使用参数1的低8位和参数2的低16位生成一个32位的密钥

        参数1:文件名(必须依据存在的文件)

        参数2:整形或者单字符数据。

        返回值:成功返回生成的密钥,失败返回-1,并置位错误码。

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/sem.h>

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

        功能:获取信号灯集合的ID

        参数1:key值         

        参数2:信号灯的个数

        参数3: IPC_CREAT:创建

                        IPC_EXCL:存在就报错。

        返回值:成功返回信号灯集合的ID,失败返回-1,并置位错误码。

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/sem.h>

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

        功能:对信号灯集合操作

         参数1:信号灯集合的ID

        参数2: struct sembuf { unsigned short sem_num; 信号灯编号

                                                short sem_op;                 信号灯的操作

                                                short sem_flg;                 信号灯操作时是否阻塞。 };

        参数3:信号灯集个数。

        返回值:成功返回0,失败返回-1,并置位错误码。

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/sem.h>

                int semctl(int semid, int semnum, int cmd, ...);

        功能:对信号灯集操作

        参数1:信号灯集ID

        参数2:要操作的信号灯编号。

        参数3: 如果想使用联合体内部的第一个成员val

                        参数3必须使用SETVAL 如果想使用联合体内部的第二个成员buf

                参数3必须使用以下:

                        IPC_STAT:获取属性

                        IPC_SET:设置属性

                        IPC_RMID: 删除信号灯集

        参数4: 如果参数3是IPC_SET或者IPC_STAT那么参数4需要加上 参数4是联合体如下:

                 union semun

                        { int val; /* SETVAL:要设置灯的值 */ 

                        struct semid_ds *buf; /* IPC_STAT, IPC_SET需要用到的信号灯信息结构体 */

                        unsigned short *array; /* GETALL, SETALL:需要用到short类型的数组 */

                        struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ };

                参数4的成员2:

                      struct semid_ds { struct ipc_perm sem_perm; /* Ownership and permissions */

                        time_t sem_otime; /* Last semop time */

                        time_t sem_ctime; /* Last change time */

                        unsigned long sem_nsems; /* No. of semaphores in set */ };

                     参数4成员2的第1个成员:

                      struct ipc_perm { key_t __key; /* Key supplied to semget(2) */

                        uid_t uid; /* Effective UID of owner */

                        gid_t gid; /* Effective GID of owner */

                        uid_t cuid; /* Effective UID of creator */

                        gid_t cgid; /* Effective GID of creator */

                        unsigned short mode; /* Permissions */

                        unsigned short __seq; /* Sequence number */ };

                    参数4的成员4:

                     struct seminfo

                        { int semmap; /* Number of entries in semaphore map; unused within kernel */

                        int semmni; /* Maximum number of semaphore sets */

                        int semmns; /* Maximum number of semaphores in all semaphore sets */

                        int semmnu; /* System-wide maximum number of undo structures; unused within kernel */

                        int semmsl; /* Maximum number of semaphores in a set */

                         int semopm; /* Maximum number of operations for semop(2) */

                        int semume; /* Maximum number of undo entries per process; unused within kernel */

                        int semusz; /* Size of struct sem_undo */

                        int semvmx; /* Maximum semaphore value */

                        int semaem; /* Max. value that can be recorded for semaphore adjustment (SEM_UNDO) */

};

eg:                

 要设置第1个灯的值为0 union semun sss; sss.val = 0; semctl(semid, 1, SETVAL,sss);          

要获取第1个灯的属性 eg: union semun sss; semctl(semid, 1, IPC_STAT,sss);

输出最后一次操作的时间:sss.buf.sem_otime;

删除信号灯集 semctl(semid, 0, IPC_RMID,NULL);

信号灯集函数的封装。

思路:将共享内存发送端申请0号灯,释放1号灯,接收端申请1号灯,释放0号灯,就可以实现读写同步。

封装:申请信号灯集封装成函数,申请灯的资源封装成函数,释放灯的资源封装成函数,

删除信号灯集封装成函数。

发送端
#include"my.h"
//1、创建key值
//2、获取共享内存段
//3、调用进程与共享内存映射
//4、发送消息
#define BACKPAGE  4096
int main(int argc, const char *argv[])
{
    int semID = getsemID(2);//申请2个信号灯
    if(semID==-1)
    {
        perror("getsemID");
        return -1;
    }

    key_t key = ftok("./",0);//创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,BACKPAGE,IPC_CREAT|0664);//获取共享内存段
    if(shmid==-1)
    {
        perror("shmget");
        return -1;
    }
    printf("shmid = %d\n",shmid);    
    char *p = shmat(shmid,NULL,0);//NULL:默认对齐 0:读写权限
    if(p==(void *)-1)
    {
        perror("shmat");
        return -1;
    }
    char buff[1024];
    while(1)
    {
        P(semID,0);//申请0号灯资源
        printf("亲输入消息:");
        fgets(buff,sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\0';//最后字符改为\0
        strcpy(p,buff);//写入内存
        if(strcmp("quit",buff)==0)
        {
            break;
        }
        V(semID,1);//释放1号灯资源
    }

    shmdt(NULL);//断开进程和共享内存的映射

    struct shmid_ds sss;

    shmctl(shmid,IPC_STAT,&sss);//获取共享内存段属性
    printf("用户ID%d\n",sss.shm_perm.uid);//输出用户ID
    int k = shmctl(shmid,IPC_RMID,NULL);//删除共享内存段
    if(k==-1)
    {
        perror("shmctl");
        return -1;
    }
    printf("删除成功\n");
    return 0;
}


接收端
#include"my.h"
//1、创建key值
//2、获取共享内存段
//3、调用进程与共享内存映射
//4、接收消息
#define BACKPAGE  4096
int main(int argc, const char *argv[])
{
    int semID = getsemID(2);
    if(semID==-1)
    {
        perror("getsemID");
        return -1;
    }
    key_t key = ftok("./",0);//创建key值
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,BACKPAGE,IPC_CREAT|0664);//获取共享内存段
    if(shmid==-1)
    {
        perror("shmget");
        return -1;
    }

    char *p = shmat(shmid,NULL,0);//NULL:默认对齐 0:读写权限
    if(p==(void *)-1)
    {
        perror("shmat");
        return -1;
    }
    char buff[1024];
    while(1)
    {
        P(semID,1);//申请1号灯资源
        printf("收到消息:%s\n",p);
        if(strcmp("quit",p)==0)
        {
            break;
        }
        V(semID,0);//释放0号灯
    }
    return 0;
}

信号灯函数的二次封装:

#include"my.h"

union semun {
    int              val;    /* SETVAL:要设置灯的值 */
    struct semid_ds *buf;    /* IPC_STAT, IPC_SET需要用到的信号灯信息结构体 */
    unsigned short  *array;  /*  GETALL, SETALL:需要用到short类型的数组 */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                (Linux-specific) */
};

int init_semno(int semID,int no)
{
    union semun sem_INFO;
    printf("请输入编号为%d的灯的值:",no);
    scanf("%d",&sem_INFO.val);
    getchar();
    if(semctl(semID,no,SETVAL,sem_INFO)==-1)
    {
        perror("semctl");
        return -1;
    }
    return 0;

}

int getsemID(int num)//申请信号灯集
{
    key_t key = ftok("./1.txt",'U');
    if(key==-1)
    {
        perror("ftok");
        return -1;
    }
    int semID;
    if((semID= semget(key,num,IPC_CREAT|IPC_EXCL|0664))==-1)
    {
        if(errno==EEXIST)//已经存在,直接打开即可
        {
            semID = semget(key,num,0664);
            return semID;
        }
        perror("semget");
        return -1;
    }
    //如果灯没有创建时给num个灯赋初值
    int i;
    for(i = 0;i<num;i++)
    {
        init_semno(semID,i);//给灯赋值的函数
    }
    return semID;//返回灯的ID
}


int P(int semID,int no)//申请资源
{
    struct sembuf SEM_op;
    SEM_op.sem_num = no;//要申请资源的信号灯编号
    SEM_op.sem_op = -1;//申请资源
    SEM_op.sem_flg = 0;//阻塞申请
    if(semop(semID,&SEM_op,1)==-1)
    {
        perror("semop");
        return -1;
    }
    return 0;
}
int V(int semID,int no)//释放资源
{
    struct sembuf SEM_op;
    SEM_op.sem_num = no;//要申请资源的信号灯编号
    SEM_op.sem_op = 1;//释放资源
    SEM_op.sem_flg = 0;//阻塞申请
    if(semop(semID,&SEM_op,1)==-1)
    {
        perror("semop");
        return -1;
    }
    return 0;
}

int rmsemID(int semID)//删除信号灯
{
    if(semctl(semID,0,IPC_RMID)==-1)//0:表示删除信号灯忽略
    {
        perror("semctl");
        return -1;
    }
    return 0;
}


#ifndef _MY_H_
#define _MY_H_
#include<myhead.h>
int getsemID(int);//申请信号灯集的函数
int init_semno(int,int);
int P(int,int);//申请
int V(int,int);//释放
int rmsemID(int);//删除信号灯集
#endif

思维导图

 

上一篇:2、C#基于.net framework的应用开发实战编程 - 设计(二、二) - 编程手把手系列文章...


下一篇:STM32内部flash分区