15.Linux实战开发3.0

15.Linux实战开发3.0

进程的通信(IPC:inter process communication)

1、概念:进程间的信息交换

2、进程通信的发生条件:

​ 数据传输

​ 资源共享

​ 信息通知

​ 进程控制

3、进程通信的6大机制:

  • 管道
  • 消息队列
  • 信号
  • 信号量
  • 共享内存
  • 网络通信
1、管道:无名管道/有名管道

无名管道:可以看作一种特殊的文件,对它的读写也可以使用普通的read、write等函数。但它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

1)特点:1、半双工,只能在具有父子关系的进程之间使用;

​ 2、一头进,一头出,先进先出;

​ 3、管道的数据只能被读一次;

​ 4、管道没有名字。

2)创建管道:pipe

fd[0]读端;fd[1]写端

int pipe(int fd[2]);//返回值:成功返回0,失败返回-1

fd[2]:管道的两个文件描述符,之后就是可以直接操作者两个文件描述符

3)对管道进行读写:read 和 write

read:是一个阻塞性(让进程挂起)的函数;scanf 就是阻塞性函数,底层就是调用了read 函数

ssize_t read(int fd, void *buf, size_t count);

函数参数:

  • fd :文件描述符
  • buf :表示读出数据缓冲区地址
  • count :表示读出的字节数

函数返回值:

若读取成功,则返回读到的字节数;若失败,返回-1;若已达到文件尾,则返回0。因此读到的字节数可能小于count的值。

write 函数和 read 函数一样

4)相关应用:

在这里插入图片描述

​ 1.利用一条管道进行信息交互

#inlcude <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{
    int fd[2];
    if(pipe(fd) == -1)//判断管道是否创建成功
    {
        perror("pipe:");
        return -1;
    }
    pid_t pid = fork();//创建子进程
    if(pid < 0)//判断创建是否成功
    {
        perror("fork:");
        return -1;
    }
    if(pid == 0)//子进程
    {
        char buff[20] = "helloworld";
        printf("child write to parent!\n");
        write(fd[1],buff,strlen(buff));//写
    }
    else//父进程
    {
        char ReadBuff[20] = {0};
        read(fd[0],ReadBuff,sizeof(buff));//读
        printf("Read from child procss: %s\n",ReadBuff);
    }
    return 0;
}
//如果是子进程读,父进程写,则需要在子进程中加入sleep函数将其挂起,等待父进程写入

如果在一个进程中使用一条管道进行读写,可能会产生自读自写的可能,所以使用双管道实现双向读写

​ 2、利用两条管道进行信息交互

在这里插入图片描述

#inlcude <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main()
{
    int fd_first[2];
    int fd_second[2];
    if(pipe(fd_first) == -1)//判断管道1创建是否成功
    {
        perror("pipe:");
        return -1;
    }
    if(pipe(fd_second) == -1)//判断管道二创建是否成功
    {
        perror("pipe:");
        return -1;
    }
    pid_t pid = fork();//创建子进程
    if (pid < 0)
    {
        perror("fork:");
        return -1;
    }
    if (pid == 0) // child
    {
        while(1)
        {
            char Buff[100] = {0};
            read(fd_first[0],Buff,sizeof(Buff));//设置向管道一进行读
            printf("read from parent process : %s\n",Buff);
        
            memset(Buff,0,100);
            printf("please input message to parent :\n");
            scanf("%[^\n]",Buff);
            while(getchar() != '\n');
            write(fd_second[1],Buff,strlen(Buff));//设置向管道二写
        }
    }
    else // parent
    {
        while(1)
        {
            char buff[100] = {0};
            printf("please input message to child!\n");
            scanf("%[^\n]",buff);
            while(getchar() != '\n');
            write(fd_first[1], buff, strlen(buff));//设置向管道一写

            memset(buff,0,100);//将buff数组里面的所有元素初始化为0
            read(fd_second[0],buff,sizeof(buff));//设置向管道二读
            printf("Read from child process : %s\n",buff);
        }
    }
    return 0;
}  
有名管道

1、特点:

​ 1)、有可见的本地文件

​ 2)、可以在两个完全不相关的程序之间进行管道通信

​ 3)、这个文件不会被删除,下次可以继续进行使用

2、有名管道和无名管道的区别:

​ 无名管道适用于有父子关系的进程之间的单向通信,而有名管道适用于不相关进程之间的双向通信。无名管道是匿名的,只在相关进程中可见,而有名管道是基于文件系统的,对应于一个特定的文件路径。

3、相关函数补充:

1)、mkfifo函数:建立实名管道

依参数pathname建立特殊的FIFO文件,该文件必须不存在。

int mkfifo(const char *__path, mode_t __mode)
//#include <sys/types.h>
//#include <sys/stat.h>

函数参数:

  • __path :绝对路径字符串
  • __mode :指定文件权限位即三位数字,或使用定义再#include<sys/stat.h>中的常值

参数返回值:

返回:成功返回0;出错为-1

2)、open函数:打开管道文件

创建文件以后,还需要打开管道才能使用;打开管道是阻塞函数,当读写端同时打开时才继续执行

int open(const char *__file, int __oflag, ...
//打开FIFO文件的四种方式
open(const char *path, O_RDONLY); // 1
open(const char *path, O_RDONLY | O_NONBLOCK); // 2
open(const char *path, O_WRONLY); // 3
open(const char *path, O_WRONLY | O_NONBLOCK); // 4

O_NONBLOCK:不阻塞 ;0:阻塞; 通过这样设置,将原本阻塞状态的IO变成不阻塞状态;

  • 利用一条管道进行通信
#incldue "SystemHead.h"
#define FIFO "MyFIFO"
int main()
{
    mkfifo(FIFO,S_IRUSR | S_IWUSR);//创建管道并赋予读写权限
    int fd = open(FIFO,O_WRONLY);//以只写的方式打开文件
    if(fd < 0)//判断打开是否成功
    {
        perror("open :");//打开文件失败,打印错误信息并返回
        return -1;
    }
    char temp[100] = "helloworld";
    write(fd,temp,strlen(temp));//向管道文件中写入字符串
    close(fd);//关闭文件
    return 0;
}
#incldue "SystemHead.h"
#define FIFO "MyFIFO"
int main()
{
    mkfifo(FIFO,S_IRUSR | S_IWUSR);//创建管道并赋予读写权限
    int fd = open(FIFO,O_RDONLY);//以只读的方式打开文件
    if(fd < 0)//判断打开是否成功
    {
        perror("open :");//打开文件失败,打印错误信息并返回
        return -1;
    }
    char temp[100] = {0};//初始化数组为0
    read(fd,temp,sizeof(temp));//从管道文件中读取字符串
    printf("read from FIFO: %s\n",temp);//打印字符串
    close(fd);//关闭文件
    return 0;
}
  • 利用两条管道进行通信
#include "SystemHead.h"
#define FIFO "MyFIFO"
#define FIFO2 "MyFIFO2"
int main()
{
    mkfifo(FIFO,S_IRUSR | S_IWUSR);//创建管道1并赋予读写权限
    mkfifo(FIFO2,S_IRUSR | S_IWUSR);//创建管道2并赋予读写权限
    int fd_first = open(FIFO,O_WRONLY);//以只写的方式打开FIFO文件
    if(fd_first < 0)//判断打开是否成功
    {
        perror("open:");//打开文件失败,打印错误信息并返回
        return -1;
    }
    int fd_second = open(FIFO2,O_RDONLY);//以只读的方式打开FIFO2文件
    if(fd_second < 0)//判断打开是否成功
    {
        perror("open:");//打开文件失败,打印错误信息并返回
        return -1;
    }
    while(1)
    {
        char temp[100] = {0};
        printf("please input message to 2 :\n");
        scanf("%[^\n]",temp);//键盘键入字符串直到输入回车结束
        while(getchar() != '\n');//去除回车
        write(fd_first,temp,strlen(temp));//写入管道
        
        memset(temp,0,100);//初始化数组
        read(fd_second,temp,sizeof(temp),sizeof(temp));//从管道中读
        printf("read from 2: %s\n",temp);
    }
    close(fd_first);
    close(fd_second);
    return 0;
}
#include ”SystemHead.h"
#define FIFO "MyFIFO"
#define FIFO2 "MyFIFO2"
int main()
{
    mkfifo(FIFO,S_IRUSR | S_IWUSR);//创建管道1并赋予读写权限
    mkfifo(FIFO2,S_IRUSR | S_IWUSR);//创建管道2并赋予读写权限
    int fd_first = open(FIFO,O_RDONLY);//以只读的方式打开FIFO文件
    if(fd_first < 0)//判断打开是否成功
    {
        perror("open:");//打开文件失败,打印错误信息并返回
        return -1;
    }
    int fd_second = open(FIFO2,O_WRONLY);//以只写的方式打开FIFO2文件
    if(fd_second < 0)//判断打开是否成功
    {
        perror("open:");//打开文件失败,打印错误信息并返回
        return -1;
    }
    while (1)
    {
        char temp[100] = {0};
        read(fd_first,temp,sizeof(temp));//从管道一中读取内容并存入数组temp中
        printf("read from 1: %s\n",temp);

        printf("please input message to 1 :\n");
        memset(temp,0,100);//初始化数组
        scanf("%[^\n]",temp);//键入字符串
        while(getchar() != '\n');//去除回车
        write(fd_second,temp,strlen(temp));//将字符串写入管道2文件中
    }
    close(fd_first);//关闭管道1
    close(fd_second);//关闭管道2
    return 0;
}

3)、functl函数:

系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性;

设置文件的阻塞特性;放开read阻塞状态,避免一些程序的阻塞

int fcntl(int __fd, int __cmd, ...)
//fcntl(fd_first[0],F_SETFL,O_NONBLOCK);
  • 清除read的阻塞状态
#include "SystemHead.h"
int main()
{
    int fd_first[2];
    if (pipe(fd_first) == -1)
    {
        perror("pipe:");
        return -1;
    }
    pid_t pid = fork();//分成父子进程
    if (pid < 0)
    {
        perror("fork:");
        return -1;
    }
    if (pid == 0) // child
    {
        while(1)
        {
            printf("I am child process!\n");
            sleep(3);
        }
    }
    else // parent
    {
        fcntl(fd_first[0],F_SETFL,O_NONBLOCK);//清空堵塞
        while(1)
        {
            char buff[100] = {0};
            printf("I am parent process!\n");
            read(fd_first[0], buff, sizeof(buff));//进入堵塞状态
            printf("Read from child process : %s\n",buff);
            sleep(3);
        }
    }
    return 0;
}
  • 清除 scanf 的阻塞状态
#include "SystemHead.h"
int main()
{
    int fd_first[2];
    if (pipe(fd_first) == -1)
    {
        perror("pipe:");
        return -1;
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork:");
        return -1;
    }
    if (pid == 0) // child
    {
        int fd_stdin = fileno(stdin);//fileno获取文件描述符
        fcntl(fd_stdin,F_SETFL,O_NONBLOCK);//清除scanf的阻塞状态
        while(1)
        {
            char temp[100] = {0};
            scanf("%s",temp);
            printf("I am child process!\n");
            sleep(3);
        }
    }
    else // parent
    {
        fcntl(fd_first[0],F_SETFL,O_NONBLOCK);
        while(1)
        {
            char buff[100] = {0};
            printf("I am parent process!\n");
            read(fd_first[0], buff, sizeof(buff));
            printf("Read from child process : %s\n",buff);
            sleep(3);
        }
    }
    return 0;
}

文件描述符:一个正整数; 可以用fileno获取文件描述符

2、消息队列:

管道数据没有类型区别,没有格式;导致管道数据只有在读出来之后才能进行解析,所以有时会取不到我们想要的数据类型

1、概念

  • 消息队列亦称报文队列,也叫做信箱。是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。
  • 消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构
  • 向消息队列中写数据,实际上是向这个数据结构中插入一个新结点;从消息队列汇总读数据,实际上是从这个数据结构中删除一个结点
  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 消息队列也有管道一样的不足,就是每个数据块的最大长度是有上限的,系统上全体队列的最大总长度也有一个上限

2、特点

​ 1)、有类型的

​ 2)、有格式的

struct Message
{
    long type;  //message type指定消息类型,为正整数
    char content[100];//指定了消息的数据。我们可以定义任意的数据类型甚至包括结构来描述消息数据,char content[100]表明消息数据是一个字符数组,我们还可以修改成其他类型。
};
typedef struct Message MSG;

3、消息队列的使用:

1)、ftok()函数

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值,用来区分它们。通常情况下,该id值通过ftok函数得到。函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。

key_t ftok(const char *__pathname, int __proj_id)

创建一个IPC键值;

函数参数:

  • pathname:文件名,指定的文件,此文件必须存在且可存取

  • **proj_id:**项目id,可以根据自己的约定,随意设置,在UNIX系统上,它的取值是1到255;

返回值:成功则返回正整数,出错:-1,错误原因存于error中

2)、msgget()函数

创建和访问一个消息队列

int msgget(key_t __key, int __msgflg);

函数参数:

  • key:键值

  • msgflg:用来确定消息队列的访问权限

​ 权限类型:IPC_CREAT IPC_EXCLIPC_NOWAIT

/*相关宏定义 */
#define IPC_CREAT	01000		/* 不存在则创建键值,如存在则打开 */
#define IPC_EXCL	02000  /*与IPC_CREAT使用,单独使用无意义。创建时,如存在则失败 */
#define IPC_NOWAIT	04000		/* 非阻塞态 ,如果需要等待则,直接返回错误*/

返回值:成功执行时,返回消息队列的唯一标识符。失败返回-1。

3)、msgsnd函数

把消息添加到消息队列中,向消息队列中发送消息

int msgsnd(int __msqid, const void *__msgp, size_t __msgsz, int __msgflg);

函数参数:

  • __msqid 消息队列标识符,由msgget函数返回。

  • __msgp 指向要发送的消息的指针,注意这里传的是void类型的指针,所以在传参的时候我们要用void*强转一下,一般是一个结构的变量的地址(也就是&msg,这里假设msg是一个结构体变量)。

  • __msgsz 消息的大小(以字节为单位)。

  • __msgflg 控制发送操作的标志,(这里填0表示以阻塞的方式发送)。

    ​ IPC_NOWAIT表示非阻塞

返回值:0成功, 且消息数据的一分副本将被放到消息队列中,失败时返回-1

4)、msgrcv函数

该函数用来从一个消息队列获取消息

ssize_t msgrcv(int __msqid, void *__msgp, size_t __msgsz, long __msgtyp, int __msgflg)

函数参数:

  • msqid: 消息队列标识符,由msgget函数返回。
  • msgp: 指向要发送的消息的指针,注意这里传的是void类型的指针,所以在传参的时候我们要用void*强转一下,一般是一个结构的变量的地址(也就是&msg,这里假设msg是一个结构体变量)。
  • msgsz : 消息的大小(以字节为单位)。
  • msgtyp:用于指定请求的消息类型:
    • msgtyp=0:收到的第一条消息,任意类型。
    • msgtyp>0:收到的第一条msgtyp类型的消息。
    • msgtyp<0:收到的第一条最低类型(小于或等于msgtyp的绝对值)的消息。
  • msgflg:用于指定所需类型的消息不再队列上时的将要采取的操作:
    • 如果设置了IPC_NOWAIT,若需要等待,则调用进程立即返回,同时返回-1,并设置errno为ENOMSG
    • 如果未设置IPC_NOWAIT,则调用进程挂起执行,直至出现以下任何一种情况发生:
      • 某一所需类型的消息被放置到队列中。
      • msqid从系统只能怪删除,当该情况发生时,返回-1,并将errno设为EIDRM
      • 调用进程收到一个要捕获的信号,在这种情况下,未收到消息,并且调用进程按signal(SIGTRAP)中指定的方式恢复执行。

返回值:

调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。

5)、msgctl函数

作用:对消息队列的基本属性进行控制、修改

int msgctl(int __msqid, int __cmd, struct msqid_ds *__buf);

函数参数:

  • msqid:消息队列标识符
  • cmd:执行的控制命令

相关宏定义:

#define IPC_RMID	0		/* 删除消息队列  */
#define IPC_SET		1		/* 设置消息队列的属性,要设置的属性存储buf指向的msqid结构中  */
#define IPC_STAT	2		/* 获取消息队列的信息返回的信息,存储在buf指向的msqid结构中  */
  • buf:消息队列管理结构体,存储消息队列的属性

ipcs -q查看消息队列的信息

  • 两个消息队列进行通信
//消息队列1用于发消息
#inlcude "SystemHead.h"
typedef struct Message
{
    long type;
    char content[100];
}MSG;

int main()
{
    key_t key = ftoK("./",10);//获取键值
    if(key < 0)
    {
        perror("ftok:");
    }
    int msgid = msgget(key,IPC_CREAT | 0777);//创建消息队列,设置权限可读可写可运行
    if(msgid < 0)//判断创建是否成功
    {
        perror("msgget:");//失败打印错误信息返回-1
        return -1;
    }
    MSG msg;//创建消息队列结构体用于发送消息
    msg.type = 1;//指定消息类型
    strcpy(msg.content,"helloworld");//初始化发送的内容
    if(msgsnd(msgid,&msg,sizeof(MSG),0) == -1)//判断发送是否成功
    {
        perror("MessageSend:");//失败打印错误信息并返回-1
        return -1;
	}
    return 0;
}
//消息队列2用于接收消息
#include "SystemHead.h"
typedef struct Message
{
    long type;
    char content[100];
}MSG;
int main()
{
    key_t key = ftok("./",10);//消息队列设置必须相同,否则无法读取
    if(key < 0)
    {
        perror("ftok:");//失败打印错误信息
    }
    int msgid = msgget(key,IPC_CREAT | 0777);//设置权限
    if(msgid < 0)
    {
        perror("msgget:");//失败打印错误信息
        return -1; 
    }
    MSG msg;//创建消息队列用于接收内容
    msgrcv(msgid,&msg,sizeof(MSG),1,0);//接收消息
    printf("read from 1: %s\n",msg.content);//读取消息
    msgctl(msgid,IPC_RMID,NULL);//删除消息队列
    return 0;
}
3、信号:

信号:一种软件层次的中断,是一种特殊的IPC(进程间通讯),它是系统里面已经设计好了的,我们只能去使用它,且是一种异步通信方式;

中断:CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,避免轮询对CPU效率的损耗;

轮询:当没有中断的时候,我们想要确定一个任务的结束,就得通过轮询不断地访问CPU查看任务什么时候结束。

1、信号的分类

# kill -l
//不可靠信号
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) SIGRTMIN    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

常见信号宏 num 信号对应的作用
(1)SIGINT 2 Ctrl+Ch或者DELETE时OS送给【前台】进程组中【每个】进程
(2)SIGABRT 6 调用abort函数,进程异常终止
(3)SIGPOLL / SIGIO 8 指示一个异步IO事件,在高级IO中提及
(4)SIGKILL 9 杀死进程的终极办法
(5)SIGSEGV 11 无效存储访问时OS发出该信号
(6)SIGPIPE 13 涉及(异步通信的)管道和socket
(7)SIGALARM 14 涉及alarm函数的实现
(8)SIGTERM 15 kill命令发送的OS默认终止信号
(9)SIGCHLD 17 子进程终止或停止时OS向其父进程发此信号等待父进程回收

2、信号发送相关函数

1)、kill()函数

可以用于将参数sig指定的信号传送给参数pid指定的进程

int kill(pid_t pid,int sig);

函数参数:

  • pid>0:将信号传给进程识别码为pid的进程
  • pid=0:将信号传给和目前进程相同进程组的所有进程
  • pid=-1:将信号广播传送给系统内所有进程。
  • pid<0:将信号传给进程组识别码为pid绝对值的所有进程。

函数返回值:

执行成功返回0,执行失败返回-1

#include "SystemHead.h"
int main()
{
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork:");
        return -1;
	}
    if(pid == 0)
    {
        while(1)
        {
            sleep(2);
            printf("child process is running!\n");
            pause();//子进程会停在此处
        }
    }
    else
    {
        sleep(5);
        printf("send signal to child !\n");
        kill(pid,SIGINT);//中断子进程
        wait(NULL);
    }
    return 0;
}//输出结果:child process is running!
//send signal to child !

2)、alarm()函数

设置信号传送闹钟,用来设置信号SIGALRM,在经历过指定的参数second秒数后传送给目前的进程。

unsigned int alarm(unsigned int __seconds)

函数参数:

  • second:指定的秒数,如果参数为0,则之前设置的闹钟会被取消,并将剩下的时间返回
#include "SystemHead.h"
int main()
{
    alarm(5);
    while(1)
    {
        printf("I am running!\n");
        sleep(1);
    }
    return 0;
}

执行结果:

在这里插入图片描述

3)、abort函数

abort 函数的作用是异常终止一个进程,意味着 abort() 后面的代码将不再执行,abort()函数是一个比较严重的函数,当调用它时,会导致程序异常终止,而不会进行一些常规的清除工作,比如释放内存等

void abort(void)
#include "SystemHead.h"
int main()
{
	int num = 0;
    while(1)
    {
        printf("I am running!\n");
        sleep(1);
        num++;
        if(num == 5)
        {
            abort();
        }
    }
    return 0;
}

4)、signal()函数—自定义信号处理方式

用于传输信号给指定的进程,会依照指定的信号编号来设置该信号的处理函数,当指定的信号到达时就会跳转到参数handler指定的函数执行

__sighandler_t signal(int __sig, __sighandler_t __handler)

函数参数:

  • sig:指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
  • handler:描述了与信号关联的动作,它可以取以下三种值:
    • ​ SIG_IGN:忽略信号
    • ​ SIG_DFL:按照系统默认方式处理
    • ​ sighandler_t类型的函数指针
#include "SystemHead.h"
void handler(int signal)
{
    if(signal == SIGINT)
    {
        printf("get SIGINT!\n");
    }
    else if(signal == SIGQUIT)
    {
        printf("get SIGQUIT!\n");
    }
    else if(signal == SIGTSTP)
    {
        printf("get SIGTSTP!\n");
    }
}
int main()
{
    signal(SIGINT,SIG_IGN);
    signal(SIGQUIT,handler);
    signal(SIGTSTP,handler);

    while(1)
    {
        printf("I am running!\n");
        sleep(3);
    }
    return 0;
}

3、补充知识点:

可重入函数:主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入 OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

满足下列条件的函数多数是不可重入的:

  • 函数体内使用了静态(static)全局变量的数据结构;
  • 函数体内调用了 malloc() 或者 free() 函数;
  • 函数体内调用了标准 I/O 函数;

优点:自成一体;允许任何时候被中断,不会影响原本的代码逻辑

4、信号量:公共的计数器(资源)
PV: p---->-1		//小于等于0的时候,进程会被阻塞
    v---->+1		//大于0的时候,进程会执行

信号量(semaphore)是变量,是一种特殊的变量。它紧取正值。对信息号量的操作只有2中:等待(wait)和发送信号(signal).

1、semget函数

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

int semget(key_t __key, int __nsems, int __semflg)
  • __key:进程间通信的键值
  • __nsems:信号量的数量
  • __semflg:创建的权限 IPC_CREAT | 0666

返回值:信号量标识符semID

2、semctl函数

信号量的控制,我们可以用来删除信号量或者初始化信号量

int semctl(int __semid, int __semnum, int __cmd, ...)
  • __semid:信号量的标识符,semget()函数的返回值
  • __semnum:信号量的下标;0是第一个信号量的下标
  • __cmd:setval:设置初始值
IPC_STAT//读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET//设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID//将信号量集从内存中删除。

返回值:成功返回0,失败返回-1

3、semop函数

信号量操作函数:

int semop(int __semid, struct sembuf *__sops, size_t __nsops)
  • __semid:信号量的标识符;
  • __sops:sembuf结构体指针(起始地址)所以可能对多个信号量操作,所以要设置数量
  • __nsops:结构体数量(>=1)
struct sembuf{
    short sem_num; // 除非使用一组信号量,否则它为0
    short sem_op;  // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                   // 一个是+1,即V(发送信号)操作。
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                   // 并在进程没有释放该信号量而终止时,操作系统释放信号量
};

函数返回值:成功返回0,失败返回-1

注意:不允许其他进程打扰自己的进程的运行,在非可重入函数的时候使用;无法保证优先级;必须要保证非可重入操作;

  • 信号量的应用:
#include "SystemHead.h"
void PV(int semID,int op)
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_flg = SEM_UNDO;
    buf.sem_op = op;
    semop(semID,&buf,1);//对信号量进行操作
}
int main()
{
    key_t key = ftok("./",20);
    int semID = semget(key,1,IPC_CREAT | 0666);
    if(semID < 0)
    {
        perror("semGet:");
    }

    semctl(semID,0,SETVAL,1);//设置第一个信号量的初始值为1

    pid_t pid = fork();//创建子进程
    if(pid <0)
    {
        perror("fork: ");
        return -1;
    }
    if(pid == 0)
    {   
        while(1)
        {
            PV(semID,-1);//申请信号量
            printf("I am child process!\n");
            sleep(2);
            printf("child am awake!\n");
            PV(semID,1);//释放信号量
        }
    }
    else
    {
        while(1)
        {
            PV(semID,-1);
            printf("I am parent process!\n");
            sleep(2);
            printf("parent is awake!\n");
            PV(semID,1);
        }
    }
    semctl(semID,0,IPC_RMID);
    return 0;
}
5、共享内存:ipcs -m

共享内存允许两个不相关的进程访问同一个逻辑内存;它是最常用的IPC,最快的IPC;直接将物理地址映射给它,(内存条代表物理地址大小)

在这里插入图片描述

补充知识:
虚拟内存技术:每个进程创建加载的时候,会被分配一个大小为4G的连续的虚拟地址
空间,其实这个地址空间时不存在的,仅仅是每个进程“认为”自己拥有4G的内存,而
实际上,它用了多少空间,操作系统就在磁盘上划出多少空间给它,等到进程真正运
行的时候,需要某些数据并且数据不在物理内存中,才会触发缺页异常,进行数据拷贝

1、shmget():创建共享内存:

int shmget(key_t __key, size_t __size, int __shmflg)

函数参数:

  • __key:0(IPC_PRIVATE)—>会建立新共享内存对象

    ​ 一般源于ftok()返回的IPC键值

  • __size:共享存储段的字节数

  • __flag:读写的权限

返回值:成功返回共享存储的id,失败返回-1。

2、shmat()函数

允许本进程访问一块共享内存的函数;将创建好的共享内存连接到某个进程,并指定内存空间。

void *shmat(int __shmid, const void *__shmaddr, int __shmflg);
  • __shmid:shmget返回的共享内存标识

  • __shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置

    • 1.如果shmaddr 是NULL,系统将自动选择一个合适的地址
    • 2.如果shmaddr 不是NULL 并且没有指定SHM_RND;则此段连接到addr所指定的地址上。
    • 3.如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上。
  • __shmflg0:读写权限

    SHM_RDONLY:只读权限

    SHM_RND:shmadrr共享内存地址非空的时候才有效

返回值:调用成功,返回一个指针,指针指向共享内存的第一个字节,如果失败,则返回-1。

3、shmdt()函数

分离共享内存地址

int shmdt(const void *__shmaddr)
  • __shmaddr:连接的共享内存的起始地址

返回值:为0,成功;不为0,失败

4、shmctl()函数

共享内存控制函数

int shmctl(int __shmid, int __cmd, struct shmid_ds *__buf)

函数参数:

  • __shmid:共享内存标识符
  • __cmd:操作字段
  • __buf:共享内存管理结构体

_cmd 相关宏定义:

#define IPC_RMID 0		/*删除*/
#define IPC_STAT 2		/*备份共享内存到_buf*/
#define IPC_SET 1		/*把_buf*存到共享内存*/
#define SHM_LOCK 11		/*锁住共享内存:禁止内存交换,不运行把内存映射到虚拟内存中,保证共享内存的效率*/
#define SHM_UNLOCK 12	/*解锁共享内存*/

函数返回值:

成功返回0;失败返回-1;

  • 两个进程间通过共享内存进行通信
#include "SystemHead.h"
//进程1
int main()
{
    key_t key = ftok("./", 12);//获取键值
    if (key < 0)
    {
        perror("ftok:");
    }
    int shmID = shmget(key, 1024, IPC_CREAT | 0666);//创建共享内存
    printf("shmid : %d\n", shmID);
    void *text = shmat(shmID, NULL, 0);//用void*指针接住返回的指针
    strcpy((char *)text, "helloworld");
    if (shmdt(text) != 0)//分离共享内存
    {
        perror("shmdt:");
    } 
    return 0;
}
#include "SystemHead.h"
//进程2
int main()
{
    key_t key = ftok("./",12);
    if(key < 0)
    {
        perror("ftok:");
    }
    int shmID = shmget(key,1024,IPC_CREAT | 0666);
    printf("shmid : %d\n",shmID);
    void *text = shmat(shmID,NULL,0);
    printf("read from shm: %s\n",(char*)text);
    if(shmdt(text) != 0)
    {
        perror("shmdt:");
    }
    shmctl(shmID,SHM_LOCK,NULL);//删除共享内存
    return 0;
}
6、网络通信:跨主机
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值