栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

求职笔记之嵌入式知识点04

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

求职笔记之嵌入式知识点04

文章目录
  • Linux进程
    • 进程概念
    • 查看进程
    • 进程创建(父进程与子进程)
    • 进程状态
      • 僵尸进程
      • 孤儿进程
  • 进程通信
    • 概述
      • 进程间通信和线程间通信区别
      • 进程间通信方式
      • 学习思路
    • 无名管道
    • 有名管道
    • 信号
      • 概念
      • 信号的发送
        • kill
        • raise
        • alarm
      • 信号的接收
        • pause
      • 信号的处理
        • signal
        • ~~**sigaction**~~
    • IPC通信
      • 共享内存
        • shmget
        • ftok
        • IPC对象的创建权限
        • shmat
        • shmdt
        • shmctl
        • 综合例子
          • 有亲缘单向通信
          • 无亲缘单向通信
          • ~~**无亲缘双向通信**~~
      • 消息队列
        • 特点
        • msgget
        • msgctl
        • msgsnd
        • msgrcv
        • 综合例子
          • 无亲缘单向通信(同一个消息队列)
          • 无亲缘双向通信(同一个消息队列)
      • 信号灯
        • 基本介绍
        • semget
        • semctl
        • semop
        • 综合例子
          • 利用信号量实现一个进程中的多线程间同步
          • 利用信号灯实现一个进程中的多线程间同步
          • 利用信号灯实现两个无亲缘关系进程间同步

Linux进程

参考博客:
https://blog.csdn.net/skrskr66/article/details/89147940 、
https://zhuanlan.zhihu.com/p/96098130

进程概念

从用户角度:进程就是一个正在运行中的程序,是程序正在运行的一个实例。
操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块。
进程是一个正在执行的程序,是资源分配的最小单位。

查看进程

输入ls /porc指令即可

前面蓝色数字代表的进程的ID。如果你想查看PID为1的进程信息,你需要查看/porc/1这个文件夹。

我们也可以使用ps -ef -aux指令来直接显示进程状态

还有getpid()和getppid()这两个函数用来查看当前程序的进程和父进程PID。

进程创建(父进程与子进程)

在Linux里,除了进程0(即PID=0的进程)以外的所有进程都是由其他进程使用系统调用fork()创建的,这里调用fork()创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。

#include 
pid_t fork(void);
返回值:
	父进程:返回值大于0;
	子进程:返回值等于0

关于fork函数需要理解,每当调用一次fork函数时,会返回两次。一次是在调用进程中(父进程)返回一次,返回值是新派生的进程的进程ID(>0)。另一次是在子进程中返回,返回值是0,代表当前进程为子进程。如果返回-1,那么则代表在创建子进程的过程中出现了错误。

例子1

如上图所示,当fork()函数调用后,父进程中的变量pid赋值成子进程的pid(pid>0),所以父进程会执行else里的代码,打印出"This is the parent",而子进程的变量pid赋值成0,所以子进程执行if(pid == 0)里的代码,打印出"This is the child"

例子2

#include 
#include    
int main()
{    
  printf("parent pid:%dn",getpid());    
  int a = 100;    
  pid_t pid = fork();    
  if(pid < 0)
  {    
    return -1;    
  }
  else if(pid == 0)//子进程中{    
    a = 20;    
    printf("child !! pid:%d----a:%d--%pn",getpid(),a ,&a);    

  }
  else//父进程
  {    
    sleep(1);    
    printf("parent !! pid:%d----a:%d--%pn",getpid(), a, &a);    
  }    
  printf("nihaoa %dn",a);    
  return 0;    
}


本例子因为有延时,先返回了子进程的pid,之后再返回了父进程的pid。fork()相当于创建了一个新的子进程,但是拷贝的是fork()函数之后的所有数据,之前的并不会拷贝。在代码之上就可以看到parentpid只打印了一次

注意:父进程子进程谁先运行?未规定。
总的来说:复制PCB,代码共享,但是子进程并非从头开始,而是从fork()函数之后开始,数据独有。

借用一下网上大佬对fork()的理解

(1)一个进程进行自身的复制,这样每个副本可以独立的完成具体的操作,在多核处理器中可以并行处理数据。这也是网络服务器的其中一个典型用途,多进程处理多连接请求。
(2)一个进程想执行另一个程序。比如一个软件包含了两个程序,主程序想调起另一个程序的话,它就可以先调用fork来创建一个自身的拷贝,然后通过exec函数来替换成将要运行的新程序。

那么创建子进程的意义是什么————压力分摊/干其他工作

在Linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的子进程。但是子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个子进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

进程状态

一个进程的生命周期可以划分为一组状态,这些状态刻画了整个进程。进程状态即体现一个进程的生命状态。一般来说,进程有五种状态:

  • 创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
  • 就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
  • 执行状态:进程处于就绪状态被调度后,进程进入执行状态
  • 阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用。
  • 终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再次执行。

这五种状态的转换如图:

在Linux内核里,进程有时候也叫做任务,下面是状态在kernel源码里的定义:

static const char * const task_state_array[] = {
"R (running)", 
"S (sleeping)", 
"D (disk sleep)", 
"T (stopped)", 
"t (tracing stop)", 
"X (dead)", 
"Z (zombie)", 
};

这些状态的具体含义是:

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
  • Z僵死状态(zombie):下文具体了解

这些当我们使用指令ps -aux就可以看到。

僵尸进程

当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。

简单来说,当子进程先于父进程退出,但是父进程并没有调用wait或waitpid函数获取子进程的状态信息,子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。上文中提到的进程的僵死状态Z(zombie)就是僵尸进程对应的状态。

例子

#include     
#include     
#include     
#include                    

int main()    
{    
    pid_t  pid;    
    //循环创建子进程    
    while(1)    
    {    
        pid = fork();    
        if (pid < 0)    
        {    
            perror("fork error:");    
            exit(1);    
        }    
        else if (pid == 0)    
        {    
            printf("I am a child process.nI am exiting.n");    
            //子进程退出,成为僵尸进程    
            exit(0);    
        }    
        else    
        {    
            //父进程休眠20s
            sleep(20);    
            continue;
        }       
    }      
  return 0;                            
}

执行这上面这个程序,子进程中途退出了,Z+这个标志就是僵尸进程的标志。

其实,僵尸进程是有危害的。进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,当一个进程一直处于Z状态,那么它的PCB也就一直都要被维护。因为PCB本身就是一个结构体会占用空间,僵尸进程会造成资源浪费,所以我们应该避免僵尸进程的产生。

僵尸进程解决方案
僵尸进程解决方案参考链接

  • (1)kill杀死父进程(一般不用)
    严格的说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵死进程的父进程。因此可以直接除掉父进程,通过kill发送SIGTERM或者SIGKILL信号。父进程结束后,僵尸进程进程变成孤儿进程,由init进程(进程号为1)充当父进程,并回收资源。
    或者运行:kill -9 父进程的pid值

  • (2)父进程用wait或waitpid去回收资源(方案不好)
    父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。

  • (3)通过信号机制,在处理函数中调用wait,回收资源
    通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待。

  • (4)fork两次
    fork两次,父进程fork一个子进程,子进程在fork出一个孙子进程,然后子进程立马退出,并由父进程去wait回收,这个过程不需要等待,然后父进程可以去干其他的活。孙子进程因为子进程退出会成为孤儿进程,那它可以由init进程(进程号为1)充当父进程,并回收。这样父进程和孙子进程就可以同时干活,互不影响,就实现了多进程。

孤儿进程

当一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

例子

#include
#include
#include
#include

int main(){ 
  pid_t pid;
  pid = fork();
  if(pid < 0){  
    perror("fork error");
    exit(1);
  }
  if(pid == 0){ 
    printf("This is the child!n");
    printf("pid = %d,ppid = %dn",getpid(),getppid();//父进程退出前的pid和ppid
    sleep(5);
    printf("npid = %d,ppid = %dn",getpid(),getppid());//父进程退出后的pid和ppid
  }else{  
    printf("This is the father!n");
    sleep(1);
    printf("father process is exited!n");
  }
  return 0;
}

运行结果:

ubuntu@VM-0-7-ubuntu:~/c_practice$ ./orphan
This is the father!
This is the child!
pid = 2338,ppid = 2337
father process is exited!
ubuntu@VM-0-7-ubuntu:~/c_practice$ 
pid = 2338,ppid = 1

孤儿进程在父进程退出后会被init进程领养,直到自己运行结束为止。这个程序很容易理解,先输出子进程的pid和父进程的pid,再然后子进程开始睡眠父进程退出,这时候子进程变成孤儿进程,再次输出时,该进程的父进程变为init进程。

孤儿进程由于有init进程循环的wait()回收资源,因此并没有什么危害。

进程通信 概述 进程间通信和线程间通信区别
  • 进程通信
    在用户空间实现进程通信是不可能的,需要通过Linux内核通信;

    //课程例子
    //说明 在用户空间实现进程通信是不可能的
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        int process_inter = 0;
        pid_t pid;
        pid=fork();
        if(pid == 0)
        {
            int i=0;
            while(process_inter==0);
            for(i=0;i<5;i++)
            {
                printf("this is child:%dn",i);
                usleep(100);
            }
        }
        else if(pid > 0)
        {
            int i=0;
            for(i=0;i<5;i++)
            {
                printf("this is father:%dn",i);
                usleep(100);
            }
            process_inter=1;
        }
        while(1);
        return 0;
    }
    

    执行结果:只有父进程打印,子进程没有打印。

  • 线程间通信
    可以在用户空间就可以实现,可以通过全局变量通信。

    //课程例子
    //说明 在用户空间实现线程通信
    #include 
    #include 
    #include 
    #include 
    
    int process_inter;
    void *fun(void *var)
    {
        int j;
        while(process_inter==0);
        for(j=0;j<10;j++)
        {
            usleep(100);
            printf("this is fun j=%dn",j);
        }
    }
    int main()
    {
        int i;
        char str[]="hello linuxn";
        pthread_t tid;
        int ret;
        ret=pthread_create(&tid,NULL,fun,(void *)str);
        if(ret<0)
        {
            printf("create thread failuren");
            return -1;
        }
        for(i=0;i<10;i++)
        {
            usleep(100);
            printf("this is main fun i=%dn",i);
        }
        process_inter=1;
        while(1);
        return 0;
    }
    

    gcc thread.c -o thread -pthread

    区别-lpthread和-pthread
    -lpthread和-pthread区别参考链接

    • 添加额外的标志:在多数系统中,-pthread会被展开为“-D_REENTRANT -lpthread”,即是除了链接pthread库外,还先定义了宏_REENTRANT。定义这个宏的目的,是为了打开系统头文件中的各种多线程支持分支。
    • 现在应该使用 -pthread 而不是 -lpthread。
进程间通信方式
  • 单机模式(只有一个Linux内核)下的进程通信
    • 管道通信(管道文件是一个特殊的文件,是由队列来实现的。)
      无名管道、有名管道;
    • 信号通信
      信号通信包括信号的发送、信号的接收和信号的处理;
    • IPC(Inter-Process Communication)通信
      共享内存、消息队列和信号灯。
  • Socket通信:存在于一个网络中两个进程之间的通信(两个Linux内核)。
学习思路

每一种通信方式都是基于文件IO的思想。

  • open:功能:创建或打开进程通信对象。函数形式不一样,有的是有多个函数完成。
  • write: 功能:向进程通信对象中写入内容。函数形式可能不一样。
  • read: 功能:从进程通信对象中读取内容。函数形式可能不一样。
  • close: 功能:关闭或删除进程通信对象。形式可能不一样。
无名管道

无名管道通信首先需要使用pipe函数创建管道之后,两个进程之间才能通信。

函数形式:
int pipe(int pipefd[2])
功能:创建管道,为系统调用:unistd.h
参数:
得到的两个文件描述符(pipefd[0]和pipefd[1]):
    管道有一个读端pipefd[0]用来读和一个写端pipefd[1]用来写,这个规定不能变。
返回值:成功是0,出错是-1;

注意:
1).管道中的东西,读完了就删除了;满足队列的特点;
2).如果管道中没有东西可读,则会读阻塞;同理,存在写阻塞。

无名管道有一定的局限性。
1).属于半双工的通信方式;
2).只有具有“亲缘关系”的进程才能使用这种通信方式,也就是父进程和子进程之间。

验证写阻塞
计算出内核开辟的管道有多大。5456 5457

//课程例子
//验证写阻塞
#include 
#include 
#include 
#include 

int main()
{
    int fd[2], ret, i;
    char writebuf[]="hello linux";
    char readbuf[128]={0};
    ret=pipe(fd);
    if(ret<0)
    {
        printf("create pip failuren");
        return -1;
    }
    printf("create pip sucess fd[0]=%d,fd[1]=%dn",fd[0],fd[1]);
    
    while(i<5500)
    {
        write(fd[1],writebuf,sizeof(writebuf));
        i++;
    }
    printf("write pip endn");//如果写阻塞,这句话不会打印
    close(fd[0]);
    close(fd[1]);

    return 0;
}

实现进程通信

// 课程例子
// 实现进程通信
#include 
#include 
#include 
#include 

int main()
{
    int process_inter=0;
    pid_t pid;
    int ret;
    int fd[2];
    ret =pip(fd);
    if(ret<0)
    {
        printf("create pip failuren");
        return -1;
    }
    printf("create pip sucess fd[0]=%d,fd[1]=%dn",fd[0],fd[1]);

    pid=fork();
    if(pid == 0)
    {
        int i=0;
        read(&fd[0],&process_inter,1);//if pip empty,sleep
        while(process_inter==0);
        for(i=0;i<5;i++)
        {
            printf("this is child:%dn",i);
            usleep(100);
        }
    }
    else if(pid > 0)
    {
        int i=0;
        for(i=0;i<5;i++)
        {
            printf("this is father:%dn",i);
            usleep(100);
        }
        process_inter=1;
        write(fd[1],&process_inter,1);
    }
    while(1);
    return 0;
}
有名管道

有名管道存在实际的物理文件,可以实现无亲缘关系的通信。
所谓的有名,即文件系统中存在这个一样文件节点,每一个文件节点都有一个inode号,而且这是一个特殊的文件类型:管道类型"p"。

UNIX下可以用ls -l 命令来看到文件的权限:
普通文件"-",目录文件"d",链接文件(软连接)“l”,命名管道文件"p",socket文件"s",字符设备文件"c",块设备文件"b"。

管道文件只有inode号,文件类型为p,但不占磁盘块空间,和套接字、字符设备文件、块设备文件一样。普通文件、链接文件及目录文件,不仅有inode号,还占磁盘块空间。

注意:
mkfifo()用来创建管道文件的节点,没有在内核中创建管道。只有通过open函数打开这个文件时才会在内核空间创建管道。

创建方法:
#include 
#include 

int mkfifo(const char pathname, mode_t mode);
参数pathname:路径名,管道名称。
参数 mode:管道的权限,和umask有关系。
返回值:成功返回 0,错误返回-1。

删除方法:
#include 
int unlink(const char *pathname);

通过有名管道实现无亲缘关系进程间通信

//课程例子
//通过有名管道实现无亲缘关系进程间通信
备注:本实验由mkfifo.c、first.c、second.c构成
// 文件1:mkfifo.c 创建管道文件
#include 
#include 
#include 
#include 
int main()
{
    int ret=0;
    ret=mkfifo("./myfifo",0777)
    if(ret<0)
    {
        printf("create myfifo failuren");
        return -1;
    }
    printf("create myfifo sucessn");
    return 0;
}

//文件2:first.c
#include 
#include 
#include 
#include 
#include 
int main()
{
    int fd;
    int i=0;
    char process_inter=0;
    fd=open("myfifo",O_WRONLY);
    if(fd<0)
    {
        printf("open my fifo failuren");
        return -1;
    }
    prrintf("open my fifo sucessn");

    for(i=0;i<5;i++)
    {
        printf("this is father:%dn",i);
        usleep(100);
    }
    process_inter=1;
    sleep(5);
    write(fd,&process_inter,1);
    while(1);
    return 0;
}

//文件3:second.c
#include 
#include 
#include 
#include 
#include 
int main()
{
    int fd;
    int i=0;
    char process_inter=0;
    fd=open("myfifo",O_RDONLY);
    if(fd<0)
    {
        printf("open my fifo failuren");
        return -1;
    }
    printf("open my fifo sucessn");
    read(fd,&process_inter,1);
    while(process_inter==0);
    for(i=0;i<5;i++)
    {
        printf("this is father:%dn",i);
        usleep(100);
    }
    while(1);
    return 0;
}

练习(方法如上):
有二个进程,一个server.c 一个client.c,要求server.c负责创建有名管道文件,即server要先运行,client后运行。

信号 概念

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

  • (1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式;
  • (2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件;
  • (3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

查看当前系统可使用的信号有哪些?

kill -l

信号名含义默认操作
SIGHUP该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。终止
SIGINT该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。终止
SIGQUIT该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。终止
SIGILL该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。终止
SIGFPE该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。终止
SIGKILL该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。终止
SIGALRM该信号当一个定时器到时的时候发出。终止
SIGSTOP该信号用于暂停一个进程,且不能被阻塞、处理或忽略。暂停进程
SIGTSTP该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。暂停进程
SIGCHLD子进程改变状态时,父进程会收到这个信号。忽略
SIGABORT该信号用于结束进程。终止

信号通信的框架

  • 信号的发送(发送信号进程):kill、raise、alarm;
  • 信号的接收(接收信号进程) : pause、sleep、while(1);
  • 信号的处理(接收信号进程) : signal。
信号的发送 kill

信号发送

头文件:
    #include 
    #include 
函数原型:
    int kill(pid_t pid, int sig);
函数参数:
    pid:指定进程。
        正数:要接收信号的进程的进程号;
        0:信号被发送到所有和pid进程在同一个进程组的进程;
        -1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
    sig:要发送的信号
返回值:成功 0;失败 -1

课程例子

//文件名: mykill.c
#include 
#include 
#include 
#include 
#include 
int main(int argc,char **argv)
{
    int sig;
    int pid;
    if(argc<3)
    {
        printf("please input paramn");
        return -1;
    }
    sig=atoi(argv[1]);
    pid=atoi(argv[2]);
    printf("sig=%d,pid=%dn",sig,pid);

    kill(pid,sig);
    return 0;
}

命令行输入

./mykill pid(ps -aux 查看) 9

raise

进程向自己发送信号,相当于kill(getpid(), sig)。

头文件:
    #include 
    #include 
函数原型:
    int raise(int sig);
函数参数:
    sig:要发送的信号
返回值:成功 0;失败 -1

例子1

//文件名: myraise.c
#include 
#include 
#include 
#include 
#include 
int main(int argc,char **argv)
{
    int sig;
    int pid;
    if(argc<2)
    {
        printf("please input paramn");
        return -1;
    }
    sig=atoi(argv[1]);
    printf("sig=%dn",sig);

    printf("raise beforen");
    raise(sig);
    //exit(0);
    //_exit(0);
    printf("raise endn");
    return 0;
}

例子2

//文件名: myraise2.c
#include 
#include 
#include 
#include 
#include 
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)
    {
        sleep(8);
        while(1);
    }
    if(pid==0)
    {
        printf("raise beforen");
        raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。
        printf("raise endn");
        exit(0);
    }
    return 0;
}
运行结果:
    父进程S、子进程T 
    --8s后-->
    父进程R、子进程T

//文件名: myraise3.c
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)//父进程中,pid为子进程的pid
    {
        sleep(8);
        if(waitpid(pid,NULL,WNOHANG)==0)//非阻塞,子进程没有退出
        {
            kill(pid,9);
        }
//        wait(NULL);
        while(1);
    }
    if(pid==0)
    {
        printf("raise beforen");
        raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。
        printf("raise endn");
        exit(0);
    }
    return 0;
}
运行结果:
    父进程S、子进程T 
    --8s后-->
    父进程R、子进程Z
备注:如果加入wait(NULL),则子进程不会出现僵尸进程Z,此时运行结果:
    父进程S、子进程T 
    --8s后-->
    父进程R

文件名: myraise4.c
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)//父进程中,pid为子进程的pid
    {
        waitpid(pid,NULL,0);//阻塞,子进程没有退出,等价于wait(NULL);
		printf("i am runningn");
        while(1);
    }
    if(pid==0)
    {
        printf("raise beforen");
        raise(SIGTSTP);//该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。
        printf("raise endn");
        exit(0);
    }
    return 0;
}
alarm

alarm开启定时器,时间到后给调用alarm的进程发送一个SIGALRM信号。
默认情况下,会终止当前进程。

头文件:
    #include 
    #include 
函数原型:
    unsigned int alarm(unsigned int seconds)
函数参数:
    seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。

//文件名: myalarm.c
#include 
#include 
#include 
#include 
#include 
int main()
{
    int i;
    i=0;
    printf("alarm beforen");
    alarm(9);
    printf("alarm aftern");
    while(i<20)
    {
        i++;
        sleep(1);
        printf("process things,i=%dn",i);
    }
    return 0;
}

alarm 与 raise 函数的比较:

  • 相同点:让内核发送信号给当前进程
  • 不同点:
    • alarm 只能发送SIGALARM信号;
    • alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号。
信号的接收

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

pause

用于将调用进程挂起直到收到信号为止,进程状态为S。

头文件:
    #include 
函数原型:
    int pause(void);
返回值:成功 0;失败 -1
//文件名: mypause.c
#include 
#include 
#include 
#include 
#include 
int main()
{
    int i;
    i=0;
    printf("pause beforen");
    pause();
    printf("pause aftern");
    while(i<20)
    {
        i++;
        sleep(1);
        printf("process things,i=%dn",i);
    }
    return 0;
}
信号的处理

收到信号的进程,处理的方式:

  • 进程的默认处理方式(内核为用户进程设置的默认处理方式)
    • 忽略
    • 终止进程
    • 暂停
  • 自定义处理方式
signal
头文件:
    #include 
函数原型:
    void (*signal(int signum, void (*handler)(int)))(int);
函数参数:
    signum:指定信号
    handler:
        SIG_IGN:忽略该信号;
        SIG_DFL:采用系统默认方式处理信号;
        自定义的信号处理函数指针。
返回值:成功 设置之前的信号处理方式;失败 -1

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

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//文件名: mysignal.c
#include 
#include 
#include 
#include 
#include 
void myfun(int signum)
{
    int i=0;
    while(i<10)
    {
        printf("process signal signum=%dn", signum);
        sleep(1);
        i++;
    }
    return ;//return main
}
int main()
{
    int i;
    i=0;
    signal(14,myfun);
    printf("alarm beforen");
    alarm(9);
    printf("alarm aftern");
    while(i<20)
    {
        i++;
        sleep(1);
        printf("process things,i=%dn",i);
    }
    return 0;
}

//文件名: mysignal2.c
#include 
#include 
#include 
#include 
#include 
#include 
void myfun(int signum)
{
    int i=0;
    while(i<10)
    {
        printf("receive signal signum=%d,i=%dn", signum,i);
        sleep(1);
        i++;
    }
    return ;//return main
}
void myfun1(int signum)
{
    printf("receive signal signum=%dn", signum);
    wait(NULL);//避免僵尸进程
    return ;//return main
}
int main()
{
    pid_t pid;
    pid=fork();
    if(pid>0)
    {
        int i=0;
        signal(10,myfun);
        signal(17,myfun1);
        while(1)
        {
            printf("parent process things,i=%dn",i);
            sleep(1);
            i++;
        }
    }
    if(pid==0)
    {
        sleep(10);
        kill(getppid(),10);//父进程
        sleep(10);
        exit(0);//发送了SIGCHLD 17,等价于kill(getppid(),17);
    }
    return 0;
}
sigaction

后面添加,没讲。
https://www.cnblogs.com/clno1/p/12941316.html

IPC通信

Inter-Process Communication。常见三种:共享内存、消息队列、信号灯。

  • 常见问题:

    • IPC对象肯定是存在于内核中。用户空间的文件系统中有没有IPC的文件类型?没有。
    • 有名管道为什么能实现无亲缘关系的进程之间的通信?是因为用户空间有管道这种文件类型。
    • IPC是不是只能用于亲缘关系进程之间的通信呢?肯定不是。
    • IPC是怎样实现无亲缘关系之间的通信呢?也是保证用户空间的二个进程对内核中的同一个IPC对象的操作。(ftok)
  • 三种IPC通信的操作函数

    • 共享内存
      • shmget:
        创建或打开共享内存;
        key参数:IPC_PRIVATE时实现有亲缘关系进程通信;ftok返回值时,能实现无亲缘关系进程通信。
        shmflg参数:flag标志由两部分组成,一为IPC对象存取权限(含义同ipc_perm中的mode),一为IPC对象创建模式标志(IPC_CREAT、IPC_EXCL),两者进行|运算合成IPC对象创建权限。
      • shmat:
        建立共享内存到用户空间映射关系,读写通过标准io读写;
      • shmdt:
        删除共享内存到用户空间映射关系;
      • shmctl:
        共享内存删除,参数为IPC_RMID;
        读取或修改共享内存的属性。
    • 消息队列
      • msgget:
        创建或打开一个消息队列。key,flag同上。
      • msgsnd
        将新消息添加到消息队列。
      • msgrcv
        消息队列中数据读后,数据就不存在。
      • msgctl
        读取或修改消息队列的属性,或删除消息队列。
    • 信号灯集
      • semget:
        创建或者打开一个信号灯集;
      • semop
        P/V操作函数。
      • semctl
        用于控制信号灯集semid中的第semnum个信号灯。根据cmd的不同,可以有3或4个形参,第四个形参必须为联合体。
        读取或修改信号灯的属性,或删除信号灯集。
共享内存 shmget

创建或打开共享内存。

头文件:
#include 
#include 
#include 

函数原型:
    int shmget(key_t key, int size, int shmflg);
函数参数:
    key:IPC_PRIVATE 或 ftok的返回值
    size:共享内存区大小
    shmflg:同open函数的权限位,也可以用8进制表示法
返回值:成功:共享内存段标识符---ID---文件描述符;失败 -1

命令行查看和删除

ipcs –m
ipcrm -m shmid

使用IPC_PRIVATE创建的IPC对象, key值属性为0,和IPC对象的编号就没有了对应关系。这样毫无关系的进程,就不能通过key值来得到IPC对象的编号(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。
例子–IPC_PRIVATE

//文件名: myshmget.c
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int shmid;
    shmid=shmget(IPC_PRIVATE,128,0777);
    if(shmid<0)
    {
        printf("create share memery failure!n");
        return -1;
    }
    printf("create share memery sucess,shmid=%d!n",shmid);

    system("ipcs -m");
//    system("ipcrm -m shmid");
    return 0;
}

**直接运行有问题**
> ipcrm: failed to parse argument: 'shmid'
ftok

函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。可实现无亲缘关系通信。

头文件:
#include < sys/types.h>
#include < sys/ipc.h>

函数原型:
    char  ftok(const char *path, char key )
    参数:第一个参数:一个存在的文件或目录名,且可存取;
        第二个参数:一个字符。
    返回值:正确返回一个key值,出错返回-1

IPC_PRIVATE操作时,共享内存的key值都一样,都是0,所以使用ftok来创建key值。只要key值是一样的,用户空间的进程通过这个函数打开,则会对内核的同一个IPC对象操作。

IPC对象的创建权限

msgget、semget、shmget函数最右边的形参flag(msgget中为msgflg、semget中为semflg、shmget中shmflg)为IPC对象创建权限,三种xxxget函数中flag的作用基本相同。
IPC对象创建权限(即flag)格式为0xxx,其中0表示8位制,低三位为用户、属组、其他的读、写、执行权限(执行位不使用)。IPC对象存取权限常与下面IPC_CREAT、IPC_EXCL两种标志进行或(|)运算完成对IPC对象创建的管理,在这里姑且把IPC_CREAT、IPC_EXCL两种标志称为IPC创建模式标志。下面是两种创建模式标志在头文件中的宏定义。

#define IPC_CREAT    01000    
#define IPC_EXCL     02000     

综上所述,flag标志由两部分组成,一为IPC对象存取权限(含义同ipc_perm中的mode),一为IPC对象创建模式标志(IPC_CREAT、IPC_EXCL),两者进行|运算合成IPC对象创建权限。

备注:IPC操作时IPC_CREAT和IPC_EXCL选项的说明。以共享内存为例:
当只有IPC_CREAT选项打开时,不管是否已存在该块共享内存,则都返回该共享内存的ID,若不存在则创建共享内存;
当只有IPC_EXCL选项打开时,不管有没有该快共享内存,shmget()都返回-1;
所以当IPC_CREAT | IPC_EXCL时, 如果没有该块共享内存,则创建,并返回共享内存ID。若已有该块共享内存,则返回-1。

shmat

第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,将共享内存映射到用户空间的地址中。

void *shmat (int shmid, const void *shmaddr, int shmflg);   //malloc
参数:
    第一个参数:shmid是由shmget()函数返回的共享内存标识;
    第二个参数:shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址;
    第三个参数shmflg:一组标志位,SHM_RDONLY表示共享内存只读,默认是0,表示共享内存可读写。
返回值:
    成功:映射后的地址;失败:NULL。

共享内存特点:

  • 共享内存创建之后,一直存在于内核中,直到被删除或系统关闭;
  • 共享内存和管道不一样,读取后,内容仍在其共享内存中。
shmdt

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
将进程里的地址映射删除。

int shmdt(const void *shmaddr);
参数:
    shmaddr 共享内存映射后的地址,即shmat()函数返回的地址指针。
返回值:
    成功:0;出错:-1。
shmctl

易错: 容易敲错shmctl
与信号量的semctl()函数一样,用来控制共享内存。
删除共享内存对象,用IPC_RMID参数。

函数原型:
int shmctl(int shmid,  int cmd,  struct shmid_ds  *buf);
函数参数:
    shmid:要操作的共享内存标识符,即shmget()函数返回的共享内存标识符。
    cmd : 要采取的操作
        IPC_STAT (获取对象属性),等价于实现了命令实现了命令ipcs -m;
        IPC_SET (设置对象属性);
        IPC_RMID (删除对象) ,等价于实现了命令ipcrm -m shmid。
    buf :指定IPC_STAT/IPC_SET时用以保存/设置属性。
函数返回值:
    成功:0
    出错:-1

shmid_ds结构 至少包括以下成员
    struct shmid_ds
    {
        uid_t shm_perm.uid;
        uid_t shm_perm.gid;
        mode_t shm_perm.mode;
    };
//文件名: myshmctl.c
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int shmid;
    int key;
    char *p;
    key=ftok("./myshmget.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    shmid=shmget(key,128,IPC_CREAT | 0777);
    if(shmid<0)
    {
        printf("create share memery failure!n");
        return -2;
    }
    printf("create share memery sucess,shmid=%d!n",shmid);

    p=(char *)shmat(shmid,NULL,0);
    if(p==NULL)
    {
        printf("shmat function failure!n");
        return -3;
    }
    printf("shmat function sucessn");

    system("ipcs -m");

    // write share memory
    fgets(p,128,stdin);
    //read share memory
    printf("1,share memory data:%sn",p);
    printf("2,share memory data:%sn",p);//读两次

    shmdt(p);
    //memcpy(p,"abcd",4);//映射关系删除,报错

    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m");//再次查看

//    system("ipcrm -m shmid");
    return 0;
}

自己实现ipcrm命令

//文件名: myshmctl.c
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc,char **argv)
{
    int shmid;
    if(argc<3)
    {
        printf("please input paramn");
        return -1;
    }
    if(strcmp(argv[1],"-m")==0)
    {
        printf("delete share memoryn");
    }
    else
    {
        return -2;
    }
    shmid=stoi(argv[2]);
    printf("shmid=%dn",shmid);
    shmctl(shmid,IPC_RMID,NULL);

    system("ipcs -m");//再次查看

    return 0;
}
综合例子 有亲缘单向通信

怎样通过共享内存实现有亲缘关系进程之间的单向通信框架?
注意:当使用IPC_PRIVATE 实现有亲缘关系进程之间通信时,fork()函数一定要放在shmget()函数之后。

// 文件名:total.c
// 功能:共享内存 父子进程通信,可以不用ftok函数
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void myfun(int signum)
{
    return;
}
int main()
{
    int shmid;
    char *p;
    pid_t pid;
    shmid=shmget(IPC_PRIVATE,128,IPC_CREAT | 0777);
    if(shmid<0)
    {
        printf("create share memery failure!n");
        return -2;
    }
    printf("create share memery sucess,shmid=%d!n",shmid);

    pid=fork();
    if(pid>0)//父进程
    {
        signal(SIGUSR2,myfun);
        p=(char *)shmat(shmid,NULL,0);
        if(p==NULL)
        {
            printf("parent process!!shmat function failure!n");
            return -3;
        }

        while(1)
        {
            // write share memory
            printf("parent process!!start write share memory:n");
            fgets(p,128,stdin); 
              
            kill(pid,SIGUSR1);
            pause();//等待子进程读数据
        }
    }
    if(pid==0)//子进程
    {
        signal(SIGUSR1,myfun);
        p=(char *)shmat(shmid,NULL,0);
        if(p==NULL)
        {
            printf("child process!!shmat function failure!n");
            return -3;
        }
        while(1)
        {
            pause();//等待父进程写数据
            //read share memory
            printf("child process!!share memory data:%s",p);
            kill(getppid(),SIGUSR2);
        }
    }
    shmdt(p);
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m");

    return 0;
}
无亲缘单向通信

怎样通过共享内存实现无亲缘关系进程之间的单向通信框架?server–>client,可以通过1块共享内存实现

// 假设先运行server,再运行client
// 文件名:server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct mybuf
{
    int pid;
    char buf[124];
};
void myfun(int signum)
{
    return;
}
int main()
{
    int shmid;
    struct mybuf *p;
    int key;
    int pid;
    key=ftok("./myshmget.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    shmid=shmget(key,128,IPC_CREAT | 0777);
    if(shmid<0)
    {
        printf("create share memery failure!n");
        return -2;
    }
    printf("create share memery sucess,shmid=%d!n",shmid);

    signal(SIGUSR2,myfun);
    p=(struct mybuf *)shmat(shmid,NULL,0);
    if(p==NULL)
    {
        printf("parent process!!shmat function failure!n");
        return -3;
    }

    // 获得client pid
    p->pid=getpid();//write server pid to share memory
    pause();//wait client read server pid
   
    pid=p->pid;

    while(1)
    {
        // write share memory
        printf("parent process!!start write share memory:n");
        fgets(p->buf,128,stdin); 
            
        kill(pid,SIGUSR1);
        pause();//等待client进程读数据
    }
   
    shmdt(p);
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m");

    return 0;
}

// 文件名:client.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct mybuf
{
    int pid;
    char buf[124];
};
void myfun(int signum)
{
    return;
}
int main()
{
    int shmid;
    struct mybuf *p;
    int key;
    int pid;
    key=ftok("./myshmget.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    shmid=shmget(key,128,IPC_CREAT | 0777);
    if(shmid<0)
    {
        printf("create share memery failure!n");
        return -2;
    }
    printf("create share memery sucess,shmid=%d!n",shmid);

    signal(SIGUSR1,myfun);
    p=(struct mybuf *)shmat(shmid,NULL,0);
    if(p==NULL)
    {
        printf("parent process!!shmat function failure!n");
        return -3;
    }

    pid=p->pid;//read shared memory to get server pid
    p->pid=getpid();//write client pid to share memory
    kill(pid, SIGUSR2);//kill signal

    //client start reda data from shared memory
    while(1)
    {
        pause();//waite server write data to share memory

        printf("client process!!recive data from share memory data:%s",p->buf);

        kill(pid,SIGUSR2);//kill signal for server to write shared memory
    }
   
    shmdt(p);
    shmctl(shmid,IPC_RMID,NULL);
    system("ipcs -m");

    return 0;
}
无亲缘双向通信

怎样通过共享内存实现无亲缘关系进程之间的双向通信框架?可以通过2块共享内存实现。

后面补充。。。
消息队列 特点
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
  • 与无名管道、有名管道一样,从消息队列中读出消息,消息队列中数据会被删除;
  • 消息队列允许一个或多个进程同时读写消息,并且每条消息都有类型。由于消息可以定义很多类型,取出时可以指定哪个消息类型取出,所以消息队列解决了共享内存多个进程同时读写时数据混乱的问题,只要把消息标定不同的类型即可;
  • 只有内核重启或人工删除时,该消息才会被删除,若不人工删除消息队列,消息队列会一直存在于内存中。
  • 通过消息队列标识符来标识消息队列。消息队列在整个系统中是唯一的。
msgget

创建或打开一个消息队列。
msgget系统调用会首先在所有既有的消息队列中搜索与指定Ke值相同的队列,如果找到了,则返回该实例的标识符(在msgflg同时使用IPC_CREAT和IPC_EXCL会返回错误)。如果没有,且指定了IPC_CREAT,那么就会创建一个新队列,并返回该队列的标识符。

头文件:
#include 
#include 
#include 

函数原型:
    int msgget(key_t key, int flag);
函数参数:
   key:和消息队列关联的key值
        可以指定为IPC_PRIVATE用于父子进程间通信);
        或者 ftok()函数创建。
   flag:消息队列的访问权限
        IPC_CREAT:创建;
        IPC_EXCEL:如果已经存在则返回失败;
        此外,如果是创建消息队列时,还需要额外加上该消息队列实例对不同用户的权限,比如 | 0600。否则会导致访问权限错误。

返回值:成功:消息队列ID(非零整数);失败 -1

命令行查看和删除

ipcs –q
ipcrm -q msgqid

msgctl

易错: 容易敲错msgctl
对消息队列进行各种控制,如修改消息队列的属 性,或删除消息消息队列。

头文件:
#include 
#include 
#include 

函数原型:
    int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
函数参数:
    msqid:消息队列的队列ID
    cmd:
        IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中;
        IPC_SET:设置消息队列的属性。这个值取自buf参数;
        IPC_RMID:从系统中删除消息队列。
    buf:消息队列缓冲区。msqid_ds数据类型的地址,用来存放或更改 消息队列的属性

返回值:成功:0;失败 -1
msgsnd

易错: 容易敲错msgsnd
将新消息添加到消息队列。

头文件:
#include 
#include 
#include 

函数原型:
    int msgsnd(int msqid, const void *msgp, size_t size, int flag);
函数参数:
    msqid:消息队列的ID
    msgp:指向待发送消息结构体的地址。
    size:发送的消息正文的字节数。
    flag:
        IPC_NOWAIT:消息没有发送完成函数也会立即返回;
	    0:阻塞直到发送完成函数才返回。
返回值:
    成功:0;失败 -1

备注:
    1、要发送的消息存放在 msgbuf 结构体中,使用 msgp 指针指向该类型引用消息数据的内容和消息的类型。常用消息结构msgbuf如下:
    struct msgbuf
    {
        long mtype;   //消息类型
        char mtext[N]; //消息正文
    };
    2、size是msgp指向的消息正文的长度,而不是整个结构体的长度,size是不包括长整型"消息类型"成员变量的长度。

如果消息队列中有足够的空间,msgsnd()函数会立即返回,并实现发送消息到msgp指定的消息队列。
如果消息队列已满,msgflg 参数没有设置 IPC_NOWAIT 值,则 msgsnd() 函数将阻塞;如果设置了 IPC_NOWAIT 值,则msgsnd()函数调用失败,并返回 -1,直到消息队列中有空间时,函数才返回0。

msgrcv

消息队列中数据读后,数据就不存在了。

头文件:
#include 
#include 
#include 

函数原型:
    int msgrcv(int msgid,  void* msgp,  size_t  size,  long msgtype,  int  flag);
函数参数:
    msqid:消息队列的ID
    msgp:接收消息的缓冲区
    size:要接收的消息的字节数
    msgtype:
        0:接收消息队列中第一个消息; 
        大于0:接收消息队列中第一个类型为msgtyp的消息;
        小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
    flag:
        0:若无消息函数会一直阻塞
        IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。

返回值:成功:接收到的消息的长度;失败 -1
//文件名: msgrcv.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long type;
    char voltage[124];
    char ID[4];
};
int main()
{
    int readret;
    int msgqid;
    struct msgbuf sendbuf,rcvbuf;

    msgqid=msgget(IPC_PRIVATE,IPC_CREAT | 0777);
    if(msgqid<0)
    {
        printf("create message queue failure!n");
        return -1;
    }
    printf("create message queue sucess,msgqid=%d!n",msgqid);

    system("ipcs -q");

    //init sendbuf
    sendbuf.type=100;
    printf("please input a message:n");
    fgets(sendbuf.voltage,124,stdin);
    //start write message to message queue
    msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0);

    // init rcvbuf
    memset(rcvbuf.voltage,0,124);
    // start read message queue
    readret=msgrcv(msgqid,(void *)&rcvbuf,124,100,0);
    printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage);

    //delete message queue
    msgctl(msgqid,IPC_RMID,NULL);
    system("ipcs -q");

    return 0;
}
综合例子 无亲缘单向通信(同一个消息队列)
// 文件名: msgserver.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long type;
    char voltage[124];
    char ID[4];
};
int main()
{
    int key;
    int readret;
    int msgqid;
    struct msgbuf sendbuf,rcvbuf;

    key=ftok("./msgget.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    msgqid=msgget(key,IPC_CREAT | 0777);
    if(msgqid<0)
    {
        printf("create message queue failure!n");
        return -1;
    }
    printf("create message queue sucess,msgqid=%d!n",msgqid);

    system("ipcs -q");

    sendbuf.type=100;//init sendbuf type
    while(1)
    { 
        memset(sendbuf.voltage,0,124);
        printf("please input a message:n");
        fgets(sendbuf.voltage,124,stdin);
        //start write message to message queue
        msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0);
    }

    //delete message queue
    msgctl(msgqid,IPC_RMID,NULL);
    system("ipcs -q");

    return 0;
}

// 文件名:msgclient.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long type;
    char voltage[124];
    char ID[4];
};
int main()
{
    int key;
    int readret;
    int msgqid;
    struct msgbuf sendbuf,rcvbuf;

    key=ftok("./msgget.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    msgqid=msgget(key,IPC_CREAT | 0777);
    if(msgqid<0)
    {
        printf("create message queue failure!n");
        return -1;
    }
    printf("create message queue sucess,msgqid=%d!n",msgqid);

    system("ipcs -q");

    rcvbuf.type=100;//init rcvbuf type
    while(1)
    { 
        memset(rcvbuf.voltage,0,124);
        // start read message queue
        readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0);
        printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage);
    }

    //delete message queue
    msgctl(msgqid,IPC_RMID,NULL);
    system("ipcs -q");

    return 0;
}
无亲缘双向通信(同一个消息队列)

双向 同时收发

// 文件名: msgserver1.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long type;
    char voltage[124];
    char ID[4];
};
int main()
{
    int key;
    int readret;
    int msgqid;
    struct msgbuf sendbuf,rcvbuf;
    pid_t pid;

    key=ftok("./msgctl.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    msgqid=msgget(key,IPC_CREAT | 0777);
    if(msgqid<0)
    {
        printf("create message queue failure!n");
        return -1;
    }
    printf("create message queue sucess,msgqid=%d!n",msgqid);

    system("ipcs -q");

    //在消息队列之后 fork函数创建子进程 
    pid=fork();
    if(pid>0)//父进程
    {
        //负责写
        sendbuf.type=100;//init sendbuf type
        while(1)
        { 
            memset(sendbuf.voltage,0,124);
            printf("please input a message:n");
            fgets(sendbuf.voltage,124,stdin);
            //start write message to message queue
            msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0);
        }
    }
    if(pid==0)//子进程
    {
        //负责读
        rcvbuf.type=200;//init rcvbuf type
        while(1)
        {
            memset(rcvbuf.voltage,0,124);
            // start read message queue
            readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0);//阻塞方式读
            printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage);
        }   
    }

    //delete message queue
    msgctl(msgqid,IPC_RMID,NULL);
    system("ipcs -q");

    return 0;
}

// 文件名: msgclient1.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long type;
    char voltage[124];
    char ID[4];
};
int main()
{
    int key;
    int readret;
    int msgqid;
    struct msgbuf sendbuf,rcvbuf;
    pid_t pid;

    key=ftok("./msgctl.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    msgqid=msgget(key,IPC_CREAT | 0777);
    if(msgqid<0)
    {
        printf("create message queue failure!n");
        return -1;
    }
    printf("create message queue sucess,msgqid=%d!n",msgqid);

    system("ipcs -q");

    //在消息队列之后 fork函数创建子进程 
    pid=fork();
    if(pid==0)//子进程
    {
        //负责写
        sendbuf.type=200;//init sendbuf type
        while(1)
        { 
            memset(sendbuf.voltage,0,124);
            printf("please input a message:n");
            fgets(sendbuf.voltage,124,stdin);
            //start write message to message queue
            msgsnd(msgqid,(void *)&sendbuf,strlen(sendbuf.voltage),0);
        }
    }
    if(pid>0)//父进程
    {
        //负责读
        rcvbuf.type=100;//init rcvbuf type
        while(1)
        {
            memset(rcvbuf.voltage,0,124);
            // start read message queue
            readret=msgrcv(msgqid,(void *)&rcvbuf,124,rcvbuf.type,0);//阻塞方式读
            printf("readret=%d,rcv:%sn",readret,rcvbuf.voltage);
        }   
    }

    //delete message queue
    msgctl(msgqid,IPC_RMID,NULL);
    system("ipcs -q");

    return 0;
}
信号灯 基本介绍

1.信号灯和POSIX规范中的信号量之间的区别是什么呢?
POSIX信号量针对于单个信号量sem_init、sem_wait、sem_post,即POSIX规范中的信号量只能作用于一个信号量(当然这个信号量对应的资源可能不止一个),每个信号对应一个P操作和V操作;而System V信号灯是一个集合,它是信号灯的集合,它含有多个信号量,它可以对多个信号量同时进行P/V操作。

2.为什么需要信号灯,只有POSIX信号量不是足够了吗?
只有POSIX信号量是不够的,考虑这样一个场景:

  • 线程A中和线程B都需要访问共享资源1和共享资源2。线程A中会需要先申请共享资源1,然后再申请共享资源2;但是在线程B中,会先申请共享资源2,然后再申请共享资源1;当线程A中开始申请共享资源1时,紧接着会申请共享资源2;而此时线程B中开始申请共享资源2时,紧接着会申请共享资源1;线程B正在占用着共享资源2,线程A正在占着共享资源1,导致线程B申请不到共享资源1,它就不会释放共享资源2;线程A申请不到共享资源2,它就不会释放共享资源1;这样就造成了死锁。
  • 当然这种情况可以这样避免,每次申请完一个共享资源时,申请结束就释放,而不能同时申请多个共享资源,多个申请成功之后,在释放。当然也可以使用今天的信号灯来解决这个问题。

信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。信号灯相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。
信号灯集合(可以包含多个信号量),IPC对象是一个信号灯集(多个信号量)。

功能信号量(POSIX)信号灯集(IPC)
定义信号变量sem_t sem1;semget
初始化信号量sem_initsemctl
P操作sem_waitsemop
V操作sem_postsemop
semget

创建或者打开一个信号灯集(注意:是信号灯集,不是信号灯!)

头文件:
#include 
#include 
#includde 

函数原型:
    int semget(key_t key, int nsems, int semflag)
函数参数:
    key 和信号灯集关联的key值
        可以指定为IPC_PRIVATE用于父子进程间通信);
        或者 ftok()函数创建。
    nsems:  信号灯集中包含的信号灯数目
    semflg:信号灯集的访问权限

返回值:成功:信号灯集ID;失败 -1

命令行查看和删除

ipcs –s
ipcrm -s semid

semctl

用于控制信号灯集semid中的第semnum个信号灯。根据cmd的不同,可以有3或4个形参,第四个形参必须为联合体。

头文件:
#include 
#include 
#includde 

函数原型:
    int semctl ( int semid, int semnum,  int cmd,...union semun arg(不是地址));
函数参数:
    semid:信号灯集ID
    semnum: 要修改的信号灯集编号,删除操作时,这个值可以设置为任意值;
    cmd:
        GETVAL:获取信号灯的值;
        SETVAL:设置信号灯的值;
        IPC_RMID:从系统中删除信号灯集合;

    union semun
    {
        int val; //SETVAL:设置信号灯的值
        struct semid_ds *buf;// IPC_STAT  (获取对象属性)
                             //IPC_SET (设置对象属性)
        unsigned short  *array;  
        struct seminfo  *__buf;  
    };

返回值:成功:0;失败 -1
//文件名: semctl.c
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int semid;
    semid=semget(IPC_PRIVATE,3,IPC_CREAT | 0777);
    if(semid<0)
    {
        printf("create sem failure!n");
        return -1;
    }
    printf("create sem sucess,semid=%d!n",semid);

    system("ipcs -s");

    //delete semaphore
    semctl(semid,0,IPC_RMID,NULL);
    system("ipcs -s");
    return 0;
}

SETVAL:设置信号灯的值 信号灯的初始化,类似sem_init;

semop

P/V操作函数

头文件:
#include 
#include 
#includde 

函数原型:
    int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
函数参数:
    semid:信号灯集ID
    struct sembuf {
        short  sem_num;  // 要操作的信号灯的编号
        short  sem_op;   //  0 :  等待,直到信号灯的值变成0
                            // 1 :  释放资源,V操作
                            //   -1 :  分配资源,P操作                    
        short  sem_flg;    // 0阻塞,  IPC_NOWAIT非阻塞, SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。
    };
    nops:  要操作的信号灯的个数

返回值:成功:0;失败 -1
综合例子 利用信号量实现一个进程中的多线程间同步
//文件名: semaphore.c
//利用信号量实现一个进程中的多线程间同步
#include 
#include 
#include 
#include 
#include 
struct sem_t sem;
void *fun(void *var)
{
    int j;
    //p
    sem_wait(&sem);//sleep
    for(j=0;j<10;j++)
    {
        usleep(100);
        printf("this is fun j=%dn",j);
    }
}
int main()
{
    int i;
    char str[]="hello linuxn";
    pthread_t tid;
    int ret;
    sem_init(&sem,0,0);//初始化信号量为0
    ret=pthread_create(&tid,NULL,fun,(void *)str);
    if(ret<0)
    {
        printf("create thread failuren");
        return -1;
    }
    for(i=0;i<10;i++)
    {
        usleep(100);
        printf("this is main fun i=%dn",i);
    }
    //v
    sem_post(&sem);
    while(1);
    return 0;
}
利用信号灯实现一个进程中的多线程间同步
//文件名: sem.c
//利用信号灯实现一个进程中的多线程间同步
#include 
#include 
#include 
#include 
#include 
#include 
union semun {
    int              val;    
    struct semid_ds *buf;    
    unsigned short  *array;  
    struct seminfo  *__buf;  
};
union semun mysemun;
struct sembuf mysembuf;
int semid;
void *fun(void *var)
{
    int j;
    //p
    mysembuf.sem_op=-1;
    semop(semid,&mysembuf,1);

    for(j=0;j<10;j++)
    {
        usleep(100);
        printf("this is fun j=%dn",j);
    }
}
int main()
{
    int i;
    char str[]="hello linuxn";
    pthread_t tid;
    int ret;
    //create sem
    semid=semget(IPC_PRIVATE,3,IPC_CREAT | 0777);
    if(semid<0)
    {
        printf("create sem failure!n");
        return -1;
    }
    printf("create sem sucess,semid=%d!n",semid);

    //init sem
    mysemun.val=0;
    semctl(semid,0,SETVAL,mysemun);

    //init mysembuf
    mysembuf.sem_num=0;
    mysembuf.sem_flg=0;

    ret=pthread_create(&tid,NULL,fun,(void *)str);
    if(ret<0)
    {
        printf("create thread failuren");
        return -1;
    }
    for(i=0;i<10;i++)
    {
        usleep(100);
        printf("this is main fun i=%dn",i);
    }
    //v
    mysembuf.sem_op=1;
    semop(semid,&mysembuf,1);

    while(1);
    return 0;
}

注意: mysembuf.sem_flg 容易敲错

利用信号灯实现两个无亲缘关系进程间同步
//备注:程序运行顺序:sem2-->sem1;
//     打印顺序:sem1-->sem2
//文件名: sem1.c
#include 
#include 
#include 
#include 
#include 
union semun {
    int              val;    
    struct semid_ds *buf;    
    unsigned short  *array;  
    struct seminfo  *__buf;  
};
union semun mysemun;
struct sembuf mysembuf;
int semid;

int main()
{
    int i;
    char str[]="hello linuxn";
    int ret;
    int key;

    //create key and sem
    key=ftok("./sem.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    semid=semget(key,3,IPC_CREAT | 0777);
    if(semid<0)
    {
        printf("create sem failure!n");
        return -2;
    }
    printf("create sem sucess,semid=%d!n",semid);

    //sem2先运行,已经初始化了sem
    // //init sem 
    // mysemun.val=0;
    // semctl(semid,0,SETVAL,mysemun);

    //init mysembuf
    mysembuf.sem_num=0;
    mysembuf.sem_flg=0;

    for(i=0;i<10;i++)
    {
        usleep(100);
        printf("this is sem1 fun i=%dn",i);
    }
    //sem1先打印,打印结束,进行v操作。
    //v
    mysembuf.sem_op=1;
    semop(semid,&mysembuf,1);

    while(1);
    return 0;
}

//文件名: sem2.c
#include 
#include 
#include 
#include 
#include 
union semun {
    int              val;    
    struct semid_ds *buf;    
    unsigned short  *array;  
    struct seminfo  *__buf;  
};
union semun mysemun;
struct sembuf mysembuf;
int semid;

int main()
{
    int i;
    char str[]="hello linuxn";
    int ret;
    int key;

    //create key and sem
    key=ftok("./sem.c",'a');
    if(key<0)
    {
        printf("create key failure!n");
        return -1;
    }
    printf("create key sucess,key=%d!n",key);

    semid=semget(key,3,IPC_CREAT | 0777);
    if(semid<0)
    {
        printf("create sem failure!n");
        return -2;
    }
    printf("create sem sucess,semid=%d!n",semid);

    //sem2代码先运行,先初始化sem,然后p操作。
    //init sem
    mysemun.val=0;
    semctl(semid,0,SETVAL,mysemun);
    //init mysembuf
    mysembuf.sem_num=0;
    mysembuf.sem_flg=0;
    //p
    mysembuf.sem_op=-1;
    semop(semid,&mysembuf,1);

    for(i=0;i<10;i++)
    {
        usleep(100);
        printf("this is sem2 fun i=%dn",i);
    }

    while(1);
    return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/277591.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号