四、并发程序设计(下)

进程间通信

  • 无名管道(pipe)
  • 有名管道 (fifo)
  • 信号(signal)
  • 共享内存(mmap)
  • 套接字(socket)
  • System V IPC
  • 共享内存(share memory)
  • 消息队列(message queue)
  • 信号灯集(semaphore set)

1. 有名管道和无名管道

1.1 无名管道(pipe)

1.1 .1无名管道特点

无名管道具有如下特点:

  • 只能用于具有亲缘关系的进程之间的通信
  • 单工的通信模式,具有固定的读端和写端
  • 无名管道创建时会返回两个文件描述符,分别用于读写管道
1.1.2 无名管道创建 – pipe

#include<unistd.h>
int pipe(int pfd[2]);

  • 成功时返回0,失败时返回EOF
  • pfd包含两个元素的整形数组,用来保存文件描述符
  • pfd[0]用于读管道;pfd[1]用于写管道
1.1.3 无名管道通信
无名管道通信
1.1.4 无名管道通信 – 示例
1.1.4.1创建两个子进程,都往无名管道里写数据,父进程读数据
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
    pid_t pid1, pid2;
    int pfd[2];
    char buff[32];
    int status;
    if(pipe(pfd) < 0){
        perror("pipe");
        return -1;
    }
    pid1 = fork();
    if(pid1 < 0){
        perror("pid1");
        return -1;
    }else if (pid1 == 0){
        strcpy(buff,"I am pid1");
        write(pfd[1],buff,32);
        exit(0);
    }else{
        pid2 = fork();
        if(pid2 < 0){
            perror("pid2");
            return -1;
        }else if(pid2 == 0){
            sleep(2);
            strcpy(buff,"I am pid2");
            write(pfd[1],buff,32);
            exit(0);
        }else{
            wait(&status);
            read(pfd[0],buff,32);
            printf("%s -- status:%d\n",buff,WEXITSTATUS(status));
            wait(&status);
            read(pfd[0],buff,32);
            printf("%s -- status:%d\n",buff,WEXITSTATUS(status));

        }

    }
    return 0;
}
1.1.4.2 观察无名管道最多能写入多少内容
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<error.h>
#include<sys/wait.h>
#include<string.h>
int main(){
    pid_t pid;
    int pfd[2];
    char buf[32] = {0};
    int re;
    re = pipe(pfd);
    if(re < 0){
        perror("pipe");
        return -1;
    }
    printf("pfd[0]=%d,pfd[1]=%d\n",pfd[0],pfd[1]);
    pid = fork();
    if(pid < 0){
        perror("fork");
        return -1;
    }else if(pid > 0){
        int j = 0;
        while(1){
            
            strcpy(buf,"hahahaha");
            for(int i = 0; i < 1000; i++){
                j++;
                if(j*strlen(buf)%1024 == 0){
                   printf("j=%d -- size:%luk\n",j,j*strlen(buf)/1024);
              }
              write(pfd[1],buf,strlen(buf));
            }
            
            sleep(1);
        }
        
    }else{
        sleep(20);
        while(1){
            re = read(pfd[0],buf,20);
            if(re > 0){
                printf("read buf = %s\n",buf);
            }else{
                printf("re = %d\n",re);
            }
        }
    }
    return 0;
}
  • 结论是最多写入64K。当读端延时读取的情况下,pipe最多容纳64K内容。
1.1.5 无名管道的读写特性:
  • 读管道:
    1.管道中有数据,read返回实际读到的字节数。
    2.管道中无数据:
       (1)管道写端被全部关闭,read返回0 (好像读到文件结尾)
       (2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
  • 写管道:
    1.管道读端全部被关闭,进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
    2.管道读端没有全部关闭:
       (1)管道已满,write阻塞。(管道大小64K)
       (2)管道未满,write将数据写入,并返回实际写入的字节数。

1.2 有名管道

  • 有名管道可以使非亲缘的两个进程互相通信
  • 通过路径名来操作,在文件系统中可见,但内容存放在
    内存中
  • 文件IO来操作有名管道
  • 遵循先进先出规则
  • 不支持lseek操作
  • 单工读写
1.2.1 有名管道创建 – mkfifo

#include<unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);

  • 成功时返回0,失败时返回EOF
  • path创建的管道文件路径
  • mode 管道文件的权限,如0666
1.2.2 有名管道读写 – 示例

进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
进程B:循环统计进程A每次写入myfifo的字符串的长度

写端:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>

int main(){
    int re, fd;
    char buf[32];
    re = mkfifo("myfifo",0666);
    if(re < 0){
        perror("mkfifo");
    //  return -1;
    }
    fd = open("myfifo",O_WRONLY);
    if(fd < 0){
        perror("open");
        return -1;
    }
    printf("after open\n");
    while(1){
        fgets(buf,32,stdin);
        write(fd,buf,strlen(buf));
    }
}

读端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main(){
    int re,fd;
    char buf[32];
    fd = open("myfifo",O_RDONLY);
    if(fd < 0){
        perror("open");
        return -1;
    }
    printf("after open read\n");
    while(1){
        re = read(fd,buf,32);
        if(re > 0){
            printf("read fifo=%s\n",buf);
        }else{
            printf("re = %d\n",re);
            sleep(1);
        }
    }
    return 0;
}
  • 注意事项:
    1.就是程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递
    2.第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的
    3.对于以只读方式(O_RDONLY)打开的FIFO文件,如果open
    调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
    4.对于以只写方式(O_WRONLY)打开的FIFO文件,如果ope调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回。

2. 共享内存(内存映射的使用、注意事项、进程间通信、systemV共享内存)

2.1 内存映射概念

  • 共享内存可以通过mmap()映射普通文件。
  • 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
  • mmap的优点:实现了用户空间和内核空间的高效交互方式

函数定义:
void *mmap(void *addr, size_t length, int prot, int flags, int fd,off_t offset);
功能:创建共享内存映射
函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED(((void *) -1)),设置errno值
参数说明:
addr:指定要映射的内存地址,一般设置为NULL让操作系统自动选择合适的内存地址。
length:必须>0。映射地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读) PROT_WRITE(可写)
, PROT_EXEC(可执行), PROT_NONE(不可访问)。
flags:由以下几个常值指定:MAP_SHARED(共享的)MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用start参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED ,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
fd:表示要映射的文件句柄。如果匿名映射写-1。
offset:表示映射文件的偏移量,一般设置为0表示从文件头部开始映射。

  • 注意事项:
    (1)创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
    (2)当MAP_SHARED时,要求:映射区的权限应<=文件打开的权限(出于对映射区的保护),如果不满足报非法参数 (Invalid argument)错误。当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
    (3)映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
    (4)用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
    (5)文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)
    (6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误,超出映射的内存大小报段错误
    (7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。


  • mmap()映射的种类:
    1.基于文件的映射
    2.匿名映射适用于具有亲缘关系的进程之间.

  • 释放内存映射
    munmap函数
    int munmap(void *addr, size_t length);
    返回值:成功返回0,失败返回-1,并设置errno值。
    函数参数:
    addr:调用mmap函数成功返回的映射区首地址
    length:映射区大小(即:mmap函数的第二个参数)

2.1.2 内存映射案例

往映射区写数据

#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>

int main(){
    void *addr;
    int fd;
    fd = open("test",O_RDWR);
    if(fd < 0){
        perror("open");
        return -1;
    }
    int len = lseek(fd,0,SEEK_END);
    addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    close(fd);
    int i = 0;
    while(i < 2048){
        memcpy((addr + i),"a",1);
        sleep(1);
        i++;
    }
    return 0;
}

从映射区读数据

#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>

int main(){
    void *addr;
    int fd;
    fd = open("test",O_RDWR);
    if(fd < 0){
        perror("open");
        return -1;
    }
    int len = lseek(fd,0,SEEK_END);
    addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    close(fd);

    while(1){
        printf("read=%s\n",(char *)addr);
        sleep(1);
    
    }
    return 0;
}

匿名映射

#include<sys/mman.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/wait.h>

int main(){
    void *addr;
    addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    if(addr == MAP_FAILED){
        perror("mmap");
        return -1;
    }
    pid_t pid;
    pid = fork();
    if(pid<0){
        perror("fork");
        return -1;
    }else if(pid > 0){
        memcpy(addr,"1234567890",10);
        wait(NULL);
    }else{
        sleep(1);
        printf("read=%s\n",(char *)addr);
    }
    munmap(addr,2048);
    return 0;
}

2.2 共享内存(sytem V)

  • 共享内存使用步骤
    – ftok函数创建Key
    – 创建/打开共享内存
    – 映射共享内存
    – 共享内存读写
    – 共享内存控制

  • System V IPC
    – IPC 对象包含: 共享内存、消息队列和信号灯集
    – 每个IPC对象有唯一的ID 用Key关联
    – IPC对象创建后一直存在,直到被显式地删除
    – ipcs / ipcrm

  • 共享内存
    – 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
    – 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
    – 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

IPC 的 key
  • ftok函数
    #include<sys/types.h>
    #include <sys/ipc.h>
    key_tftok(const char *path,int proj_id);
    – 成功时返回合法的key值,失败时返回EOF
    – path存在且可访问的文件的路径
    – proj_id用于生成key的数字,范围1-255。

  • 共享内存创建 – shmget
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, int size, int shmflg);
    – 成功时返回共享内存的id,失败时返回EOF
    – key和共享内存关联的key,PC_PRIVATE 或 ftok生成
    – shmflg 共享内存标志位IPC_CREAT|0666

  • 共享内存映射 – shmat
    #include <sys/ipc.h>
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    – 成功时返回映射后的地址,失败时返回(void *)-1
    – shmid要映射的共享内存id
    – shmaddr映射后的地址, NULL表示由系统自动映射
    – shmflg标志位 0表示可读写;SHM_RDONLY表示只读

  • 共享内存撤销映射 – shmdt
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmdt(void *shmaddr);
    – 成功时返回0,失败时返回EOF
    – 不使用共享内存时应撤销映射
    – 进程结束时自动撤销

  • 共享内存控制 – shmctl
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    – 成功时返回0,失败时返回EOF
    – shmid要操作的共享内存的id
    – cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
    – buf保存或设置共享内存属性的地址

2.2.1 共享内存案例

往共享内存写数据

#include<stdio.h>
#include<string.h>
#include<sys/shm.h>

int main(){
    key_t key;
    int shmid;
    char *buf;
    key = ftok("keytest",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }
    printf("key=%x\n",key);
    shmid = shmget(key,512,IPC_CREAT|0666);
    if(shmid<0){
        perror("shmget\n");
        return -1;
    }
    printf("shmid=%d\n",shmid);
    buf = shmat(shmid,NULL,0);
    if(buf<0){
        perror("shmat");
        return -1;
    }
    strcpy(buf,"hello world");
    return 0;
}

从共享内存读数据

#include<stdio.h>
#include<string.h>
#include<sys/shm.h>

int main(){
    key_t key;
    int shmid;
    char *buf;
    key = ftok("keytest",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }
    printf("key=%x\n",key);
    shmid = shmget(key,512,0666);
    if(shmid<0){
        perror("shmget\n");
        return -1;
    }
    printf("shmid=%d\n",shmid);
    buf = shmat(shmid,NULL,0);
    if(buf<0){
        perror("shmat");
        return -1;
    }
    printf("share mem=%s\n",buf);
    shmdt(buf);
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

3. 信号机制

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式linux内核通过信号通知用户进程,不同的信号类型代表不同的事件。
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式
– 缺省方式
– 忽略信号
– 捕捉信号

  • 信号的产生
    – 按键产生
    – 系统调用函数产生(比如raise,kill)
    – 硬件异常
    – 命令行产生 (kill)
    – 软件条件(比如被0除,访问非法内存等)

3.1 常用信号


3.1.1 信号相关命令
  • kill [-signal] pid
    默认发送SIGTERM
    -sig 可指定信号
    pid指定发送对象

  • killall [-u user | prog]
    prog指定进程名
    user指定用户名

  • kill -l
    查看所有信号类型

3.1.2 信号发送 – kill / raise

#include<unistd.h>
#include <signal.h>
int kill(pid_t pid,int sig);
int raise(int sig);//给自己发信号
– 成功时返回0,失败时返回EOF
– pid 接收进程的进程号:0代表同组进程; -1代表所有进程
– sig 信号类型

#include<signal.h>
#include<unistd.h>

int main(){
    //kill(15090,11);
    raise(11);
    return 0;
}
3.1.3 信号相关函数 – alarm / pause

int alarm(unsigned int seconds);
– 成功时返回上个定时器的剩余时间,失败时返回EOF
– seconds 定时器的时间
– 一个进程中只能设定一个定时器,时间到时产生SIGALRM

int pause(void);
– 进程一直阻塞,直到被信号中断
– 被信号中断后返回-1,errno为EINTR

#include<signal.h>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
int main(){
    //kill(15090,11);
    //raise(11);
    int ret = alarm(5);
   printf("ret1 = %d\n",ret);
    if(ret == EOF){
        perror("alarm");
        return errno;
    }
   ret =pause();
   printf("ret2 = %d\n",ret);
    return 0;
}
3.1.4 定时器函数

ualarm(循环发送)
useconds_t ualarm(useconds_t usecs, useconds_t interval);
以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生
int setitimer(int which, const struct itimerval *new_value, struct itimerval old_value);
功能:定时的发送alarm信号
参数:
which:
ITIMER_REAL:以逝去时间递减。发送SIGALRM信号
ITIMER_VIRTUAL:计算进程(用户模式)执行的时间。发送SIGVTALRM信号
ITIMER_PROF:进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间。发送SIGPROF信号
new_value:负责设定timout时间
old_value:存放旧的timeout值,一般指定为NULL
struct itimerval {
struct timeval it_interval;//闹钟触发周期
struct timeval it_value;//闹钟触发时间
};
struct timeval {
time_t tv_sec; /
seconds /
suseconds_t tv_usec; /
microseconds */
};

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
void handle(int sig){
    printf("timer sig=%d\n",sig);
}
int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    struct itimerval timevalue;
    timevalue.it_interval.tv_sec = 1;
    timevalue.it_interval.tv_usec = 0;
    timevalue.it_value.tv_sec = 5;
    timevalue.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL,&timevalue,NULL);
    sigaction(SIGALRM,&act,NULL);
    while(1){
        sleep(1);
    }
    return 0;
}

3.2 设置信号响应方式 – signal

#include<unistd.h>
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
– 成功时返回原先的信号处理函数,失败时返回SIG_ERR
– signo 要设置的信号类型
– handler 指定的信号处理函数: SIG_DFL代表缺省方式;SIG_IGN 代表忽略信号;

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h>
#include<linux/posix_types.h>
void handle(int sig){
    printf("I cath the sig = %d\n",sig);
}
int main(){
    signal(SIGINT,handle);
    while(1){
        sleep(1);
    }
    return 0;
}
3.2.1 sigaction函数:

int sigaction(int signum, const struct sigaction act,struct sigaction oldact);
struct sigaction {
   void (
sa_handler)(int);
   void (
sa_sigaction)(int, siginfo_t *, void );
   sigset_t sa_mask;
   int sa_flags;
   void (
sa_restorer)(void);
}
参数:signum:处理的信号
act,oldact:处理信号的新行为和旧的行为,是一个sigaction结构体。
sigaction结构体成员定义如下:
sa_handler:是一个函数指针,其含义与signal函数中的信号处理函数类似
sa_sigaction:另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域

3.3 使用SIGCHLD信号实现回收子进程

SIGCHLD的产生条件
1.子进程终止时
2.子进程接收到SIGSTOP信号停止时
3.子进程处在停止态,接受到SIGCONT后唤醒时

#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
void handle(int sig){
    wait(NULL);
    printf("get sig = %d\n",sig);
}
int main(){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    pid = fork();
    if(pid > 0){
        sigaction(SIGCHLD,&act,NULL);
        while(1){
            printf("this is father proccess\n");
            sleep(1);
        }
    }else if(pid == 0){
        sleep(5);
        exit(0);
    }
    return 0;
}

3.4 信号机制下(阻塞、信号集、信号驱动任务)

3.4.1 信号集、信号的阻塞
  • 有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。
  • 信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的状态:
信号递达(Delivery):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)
信号未决(Pending):从产生到递达之间的状态

3.4.2 信号集操作函数

– sigset_t set;自定义信号集。是一个32bit 64bit 128bit 的数组。
– sigemptyset(sigset_t *set); 清空信号集
– sigfillset(sigset_t *set);全部置1
– sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中
– sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除
– sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。设定对信号集内的信号的处理方式(阻塞或不阻塞)

#include <signal.h>
– int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset);
– 返回值:若成功则返回0,若出错则返回-1
– 首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
– 其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
– how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)
– SIG_BLOCK:把参数set中的信号添加到信号屏蔽字中
– SIG_UNBLOCK:从信号屏蔽字中删除参数set中的信号
– SIG_SETMASK:把信号屏蔽字设置为参数set中的信号

– int pause(void);进程一直阻塞,直到被信号中断,返回值:-1并设置errno为EINTR
函数行为:
1.如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
2.如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回
3.如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。
4.pause收到的信号如果被屏蔽,那么pause就不能被唤醒

– int sigsuspend(const sigset_t *sigmask);
– 功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行
– 参数:sigmask:希望屏蔽的信号

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

void handle(int sig){
    printf("I get sig = %d\n",sig);
}

int main(){
   struct sigaction act;
   act.sa_handler = handle;
   act.sa_flags = 0;
   sigemptyset(&act.sa_mask);
   sigaction(SIGINT,&act,NULL);
   sigset_t set;
   sigemptyset(&set);
   sigaddset(&set,SIGINT);
   sigprocmask(SIG_BLOCK,&set,NULL);
   sleep(5);
   sigprocmask(SIG_UNBLOCK,&set,NULL);
   while(1){
       sleep(1);
   }
    return 0;
}

*SIGINT信号被屏蔽等5s之后解屏蔽,然后信号抵达,执行捕获函数

3.4.3 信号驱动任务
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
    printf("this is sig = %d\n",sig);
}
void doTask(){
    printf("task start\n");
    sleep(3);
    printf("task end\n");
}
int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGHUP,&act,NULL);
    sigset_t set, set2;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGHUP);
    pause();
    while(1){
        doTask();
        pause();
    }
    return 0;
}
  • 我们发现在执行任务期间再来信号,会打断任务的处理。因此需要添加屏蔽信号

解决方法如下:

#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
    printf("this is sig = %d\n",sig);
}
void doTask(){
    printf("task start\n");
    sleep(3);
    printf("task end\n");
}
int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGHUP,&act,NULL);
    sigset_t set, set2;
    sigemptyset(&set2);
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGHUP);
    pause();
    while(1){
        sigprocmask(SIG_BLOCK,&set,NULL);
        doTask();
        sigprocmask(SIG_UNBLOCK,&set,NULL);
        pause();
    }
    return 0;
}
  • 但是又出现了新问题,虽然在执行任务期间,信号被堵塞,信号被解除堵塞之后马上抵达执行捕获函数,但是没有被pause收到,因为此时pause还没执行。所以进程马上又被堵塞,无法进行下次任务。

解决方法如下:

#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
    printf("this is sig = %d\n",sig);
}
void doTask(){
    printf("task start\n");
    sleep(3);
    printf("task end\n");
}
int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGHUP,&act,NULL);
    sigset_t set, set2;
    sigemptyset(&set2);
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGHUP);
    pause();
    while(1){
        sigprocmask(SIG_BLOCK,&set,NULL);
        doTask();
        sigsuspend(&set2); 
    //  sigprocmask(SIG_UNBLOCK,&set,NULL);
    //  pause();
    }
    return 0;
}
  • sigsuspend相当于 sigprocmask(SIG_UNBLOCK,&set,NULL)和pause()的原子操作。

4. 信号灯

4.1 信号量/灯(semaphore)

  • 概念:是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的PV操作概念:

  • 信号量代表某一类资源,其值表示系统中该资源的数量

  • 信号量是一个受保护的变量,只能通过三种操作来访问
    初始化
    P操作(申请资源)
    V操作(释放资源)

4.2 信号量-P/V操作

P(S)含义如下:
if(信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
}else {
申请资源的任务阻塞;
}
V(S)含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}

4.3 Posix信号量

  • posix中定义了两类信号量:
    – 无名信号量(基于内存的信号量,linux仅支持线程同步)
    – 有名信号量

4.4 信号灯相关函数

4.4.1 有名信号灯

sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数:
– name:name是给信号灯起的名字
– oflag:打开方式,常用O_CREAT
– mode:文件权限。常用0666
– value:信号量值。二元信号灯值为1,普通表示资源数目
– 信号灯文件位置:/dev/shm

  • 有名信号灯关闭
    int sem_close(sem_t *sem);
  • 有名信号灯的删除
    int sem_unlink(const char* name);
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/shm.h>
#include<semaphore.h>
#include<stdlib.h>
#include<fcntl.h>
int shmid;
char *shmadrr;
void delsemfile(int sig)
{
    sem_unlink("mysem_w");
    shmdt(shmadrr);
    shmctl(shmid,IPC_RMID,NULL);
    exit(0);
}
int main(){
    sem_t *sem_w, *sem_r;
    key_t key;

    struct sigaction act;
    act.sa_handler = delsemfile;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    key = ftok(".",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }

    shmid = shmget(key, 500, IPC_CREAT|0666);
    if(shmid < 0){
        perror("shmget");
        return -1;
    }

    shmadrr = shmat(shmid,NULL, 0);
    sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
    sem_r = sem_open("mysem_r",O_CREAT|O_RDWR, 0666, 0);
    while(1){
        sem_wait(sem_w);
        printf(">");
        fgets(shmadrr,500, stdin);
        sem_post(sem_r);
    }
    return 0;
}
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/shm.h>
#include<semaphore.h>
#include<stdlib.h>
#include<fcntl.h>
int shmid;
char *shmadrr;
void delsemfile(int sig){
    shmdt(shmadrr);
    shmctl(shmid,IPC_RMID,NULL);
    sem_unlink("mysem_r");
    exit(0);
}
int main(){
    sem_t *sem_w, *sem_r;
    key_t key;

    struct sigaction act;
    act.sa_handler = delsemfile;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    key = ftok(".",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }

    shmid = shmget(key, 500, 0666);
    if(shmid < 0){
        perror("shmget");
        return -1;
    }

    shmadrr = shmat(shmid,NULL, 0);
    sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
    sem_r = sem_open("mysem_r",O_CREAT|O_RDWR, 0666, 0);
    while(1){
        sem_wait(sem_r);
        printf("%s",(char *)shmadrr);
        sem_post(sem_w);
    }
    return 0;
}
4.4.2 无名信号量

#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int val);

  • 成功时返回0,失败时EOF

  • sem 指向要初始化的信号量对象

  • pshared 0 – 线程间 1 – 进程间

  • val信号量初值

  • 无名信号灯销毁
    int sem_destroy(sem_t* sem);

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<fcntl.h>
#include<pthread.h>
#include<sys/shm.h>
#include<semaphore.h>
sem_t sem_w,sem_r;
int shmid;
char *shmadrr;
void destorysem(int sig){
    shmdt(shmadrr);
    shmctl(shmid,IPC_RMID,NULL);
    sem_destroy(&sem_w);
    sem_destroy(&sem_r);
    exit(0);
}

void *readmem(void *arg){
    pthread_detach(pthread_self());
    while(1){
        sem_wait(&sem_r);
        printf("%s",shmadrr);
        sem_post(&sem_w);
    }
    pthread_exit(NULL);
}
int main(){

    key_t key;
    struct sigaction act;
    act.sa_handler = destorysem;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);

    key = ftok(".",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }
    shmid = shmget(key,500,IPC_CREAT|0666);
    if(shmid < 0){
        perror("shmget");
        return -1;
    }
    shmadrr = shmat(shmid,NULL,0);
    sem_init(&sem_w,0,1);
    sem_init(&sem_r,0,0);

    pthread_t tid;
    pthread_create(&tid,NULL,readmem,NULL);
    while(1){
        sem_wait(&sem_w);
        printf(">");
        fgets(shmadrr,500,stdin);
        sem_post(&sem_r);
    }
    return 0;
}

4.4.3 信号量 – P / V 操作

#include<semaphore.h>
int sem_wait(sem_t *sem);

  • P操作
    int sem_post(sem_t *sem);
  • V操作
    – 成功时返回0,失败时返回EOF
    – sem指向要操作的信号量对象

4.5 System V IPC - 信号灯特点

  • System V 信号灯是一个或多个计数信号灯的集
  • 可同时操作集合中的多个信号灯
  • 申请多个资源时避免死锁
4.5.1 System V信号灯使用步骤
  • 打开/创建信号灯 semget
    int semget(key_t key, int nsems, int semflg);
    – key:ftok产生的key值(和信号灯关联的key值)
    – nsems:信号灯集中包含的信号灯数目
    – semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
    – 返回值:成功:信号灯集ID;失败:-1

  • 信号灯初始化 semctl
    int semctl ( int semid, int semnum, int cmd…/union semun arg/);
    功能:信号灯集合的控制(初始化/删除)
    参数:
    – semid:信号灯集ID
    – semnum:要操作的集合中的信号灯编号
    – cmd:
    GETVAL:获取信号灯的值,返回值是获得值
    SETVAL:设置信号灯的值,需要用到第四个参数:共用体
    IPC_RMID:从系统中删除信号灯集合
    返回值:成功0;失败-1

  • P/V操作 semop
    int semop ( int semid, struct sembuf *opsptr, size_t nops);
    功能:对信号灯集合中的信号量进行P - V操作
    参数:
    – semid:信号灯集ID
    – struct sembuf {
    short sem_num; //要操作的信号灯的编号
    short sem_op;// 1 :释放资源,V操作// -1 :分配资源,P操作
    short sem_flg;// 0(阻塞),IPC_NOWAIT, SEM_UNDO
    };//对某一个信号灯的操作,如果同时对多个操作,则需要定义这种结构体数组
    – nops:要操作的信号灯的个数,1个返回值:成功:0;失败:-1

  • 删除信号灯 semctl

#include<semaphore.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<signal.h>

#define SEM_READ 0
#define SEM_WRITE 1

union semun{
    int val;
};

void Poperation(int semid,int semindex){
    struct sembuf sbuf;
    sbuf.sem_num = semindex;
    sbuf.sem_op = -1;
    sbuf.sem_flg = 0;
    semop(semid,&sbuf,1);
}

void Voperation(int semid, int semindex){
    struct sembuf sbuf;
    sbuf.sem_num = semindex;
    sbuf.sem_op = 1;
    sbuf.sem_flg = 0;
    semop(semid,&sbuf,1);
}
char *shmaddr;
int semid, shmid;
void removesem(int sig){
    shmdt(shmaddr);
    shmctl(shmid,IPC_RMID,NULL);
    semctl(semid,SEM_READ,IPC_RMID);
    semctl(semid,SEM_WRITE,IPC_RMID);
    exit(0);
}
int main(){

    key_t key;
    struct sigaction act;
    act.sa_handler = removesem;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);

    key = ftok(".",100);
    if(key < 0){
        perror("ftok");
        return -1;
    }

    semid = semget(key,2,IPC_CREAT|0666);
    if(semid < 0){
        perror("semget");
        return -1;
    }
    shmid = shmget(key,500,IPC_CREAT|0666);
    if(shmid < 0){
        perror("shmget");
        return -1;
    }
    shmaddr = shmat(shmid,NULL,0);

    union semun mysem;
    mysem.val = 0;
    semctl(semid,SEM_READ,SETVAL,mysem);
    mysem.val =1;
    semctl(semid,SEM_WRITE,SETVAL,mysem);

    pid_t pid;
    pid = fork();
    if(pid < 0){
        perror("fork");
        shmctl(shmid,IPC_RMID,NULL);
        semctl(semid,0,IPC_RMID);
        exit(-1);
    }else if(pid == 0){
        while(1){
            Poperation(semid,SEM_READ);
            printf("%s",shmaddr);
            Voperation(semid,SEM_WRITE);
        }
    }else{
        while(1){
            Poperation(semid,SEM_WRITE);
            printf(">");
            fgets(shmaddr,32,stdin);
            Voperation(semid,SEM_READ);
        }
    }
        return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容