小工具知识网

当前位置: 首页 > 行业知识 > Linux之多进程

行业知识

Linux之多进程

2026-10-26 21:04:34 来源:互联网转载

文章目录

    • c程序获取进程pid和ppid
    • 进程相关命令
    • 进程的创建
    • 多进程
    • 进程退出
      • exit()函数
      • _exit函数
      • 进程的等待
        • wait函数
        • waitpid函数
        • 进程的替换
        • 进程间的通信
          • 一、无名管道
          • 二、有名管道
          • 三、信号
            • kill函数
            • raise函数
            • pause() 函数
            • 自定义信号处理函数
            • SIGALARM信号
            • 子进程退出信号SIGCHLD
            • 四、消息队列
              • 创建
              • 删除
              • 发送
              • 接收
              • 五、共享内存
                • 创建
                • 删除
                • 映射
                • 解除映射
                • 进程间同步
                  • 信号量
                    • 创建信号量集合
                    • 设置信号量的数量
                    • 操作信号量

                      c程序获取进程pid和ppid

                      在 Linux 系统中管理进程使用树型管理方式每个进程都需要与其他某一个进程建立

                      父子关系, 对应的进程则叫做 父进程

                      Linux 系统会为每个进程分配 id , 这个 id 作为当前进程的唯一标识, 当进程结束, 则会回收

                      进程的 id 与 父进程的 id 分别通过 getpid() 与 getppid() 来获取

                      函数头文件

                      #include #include 

                      函数原型

                      pid_t getpid(void);pid_t getppid(void);

                      函数功能:

                      获取当前进程 id 与 父进程 id

                      函数返回值:

                      成功 : getpid() 返回当前进程 id ,getppid() 返回父进程 id

                      pid_t 的类型 实际为 int , 在系统中采用 typedef 的形式

                      #include#include#includeint main(){printf("pid=%d,ppid=%d",getpid(),getppid());return 0;}

                      进程相关命令

                      功能:显示当前进程的状态 (Process Status)

                      语法

                      ps [options]

                      常用语法选项

                      -A : 列出所有的进程

                      -e : 与 -A 功能类似

                      -w : 显示加宽可以显示较多的资讯

                      -au : 显示较详细的信息

                      -aux :显示所有包含其他使用者的进程

                      示例 : ps -ef 列出所有的进程,相比 ps -aux 信息要少一些

                      示例 : ps -ef | grep “可执行文件名” 根据名称查找指定名字

                      top [-] [d delay] [q] [c] [S] [s] [i] [n] [b]

                      选项

                      d : 改变显示的更新速度,或是在交谈式指令列 (interactive command) 按 s

                      q : 没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高的优先序执行

                      c : 切换显示模式,共有两种模式,一是只显示执行档的名称,另一种是显示完整的路径与名称

                      S : 累积模式,会将己完成或消失的子进程 (dead child process) 的 CPU time 累积起来

                      s : 安全模式,将交谈式指令取消, 避免潜在的危机

                      i : 不显示任何闲置 (idle) 或无用 (zombie) 的进程

                      n : 更新的次数,完成后将会退出 top

                      b : 批次档模式,搭配 “n” 参数一起使用,可以用来将 top 的结果输出到档案内

                      仅显示指定进程ID的信息

                      top -p 

                      pstree 命令是将所有的进程以树型结构的方式进行展示

                      功能:kill 命令是用于结束进程的命令或者用于显示相关信号

                      kill -9  

                      进程的创建

                      创建进程的函数需要调用 fork() 函数, 则会产生一个新的进程

                      函数头文件

                      #include #include 

                      函数原型

                      pid_t fork(void);

                      函数功能:创建一个子进程

                      函数返回值

                      成功 : 返回给父进程是子进程 的 pid , 返回给子进程的是 0

                      失败 : 返回 -1, 并设置 errno

                      示例 : 创建一个子进程,并打印 HelloWorld

                      #include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR fork():]");return -1;}printf("hello\n");return 0;}

                      通过 fork() 函数创建子进程之后,有如下特点:

                      1、父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定

                      2、子进程会拷贝父进程地址空间的内容, 包括缓冲区、文件描述符等

                      eg:

                      #include#include#includeint main(){write(1,"hello",5);fputs("hello",stdout);pid_t pid;pid=fork();if(pid==-1){perror("[ERROR fork():]");return -1;}return 0;}

                      结果打印了三个hello

                      因为fputs是有缓冲区,fork子进程后会拷贝父进程地址空间的内容

                      多进程

                      在创建多个进程时, 最主要的原则为 由父进程统一创建,统一管理,

                      不能进行递归创建

                      #include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR fork():]");exit(EXIT_FAILURE);}else if(pid==0){printf("process son A pid=%d\n",getpid());sleep(2);exit(EXIT_SUCCESS);}else if(pid>0){pid=fork();if(pid==-1){perror("[ERROR fork():]");exit(EXIT_FAILURE);}else if(pid==0){printf("process son B pid=%d\n",getpid());sleep(3);exit(EXIT_SUCCESS);}else if(pid>0){}}return 0;}

                      进程退出

                      exit()函数

                      函数头文件

                      #include 

                      函数原型

                      void exit(int status);

                      函数功能:结束进程,并刷新缓冲区

                      函数参数

                      status : 退出状态值

                      在系统中定义了两个状态值 :

                      EXIT_SUCCESS: 正常退出

                      EXIT_FAILURE: 异常退出, 具体定义在 stdlib.h 中

                      #define EXIT_FAILURE 1 #define EXIT_SUCCESS 

                      _exit函数

                      函数头文件

                      #include 

                      函数原型

                      void _exit(int status);

                      函数参数

                      status : 进程退出的状态值

                      函数功能:结束进程,不会刷新缓冲区

                      函数实例1 : exit.c

                      #include#includeint main(){    printf("using exit----\n");    printf("This is the content in buffer\n");    exit(0);}

                      执行结果为:

                      using exit----This is the content in buffer

                      函数实例2:_exit.c

                      #include#includeint main(){    printf("using _exit--\n");    printf("This is the content in buffer");    _exit(0);}

                      执行结果为 :

                      using _exit-- 

                      printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。所以exit()将缓冲区的数据写完后才退出,而_exit()函数直接退出

                      进程的等待

                      父进程 调用wait()与waitpid()函数等待子进程退出后,释放子进程遗留的资源

                      wait函数

                      函数头文件

                      #include #include 

                      函数原型

                      pid_t wait(int *wstatus);

                      函数功能:让函数调用者进程进入到睡眠状态, 等待子进程进入僵***状态后,释放相关资源并返回

                      函数参数:

                      wstatus : 保存子进程退出状态值变量的指针,获取具体值需要使用WEXITSTATUS(status)宏定义

                      函数返回值:

                      成功 : 返回退出子进程的 pid

                      失败 :返回 -1

                      示例:创建一个子进程,延时3s后退出,父进程等待子进程后退出

                      #include#include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("the child process pid=%d\n",getpid());sleep(3);exit(66);}else if(pid>0){int status=0;pid_t cpid=wait(&status);if(cpid==-1){perror("[ERROR] wait():");exit(EXIT_FAILURE);}printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}return 0;}

                      打印结果:

                      the child process pid=17596the child process pid=17596 died status=66

                      在 wait 存储在 satus 变量的值, 存储了很多信息, 通过一系列 W 开头的宏来解析获取

                      WIFEXITED(status) : 进程是否正常结束

                      WEXITSTATUS(wstatus) : 获取进程退出状态值, 即exit 函数的参数

                      WIFSIGNALED(wstatus) : 表示该子进程是否被信号结束的, 返回真,则表示被信号结束的

                      WTERMSIG(wstatus) : 返回结束该子进程的那个信号的信号值

                      WCOREDUMP(wstatus) : 表示该子进程被信号唤醒的

                      WIFSTOPPED(wstatus) : 表示该子进程是否被信号中止 (stop) 的 , 返回真,则表示是被信号中止的

                      waitpid函数

                      函数头文件

                      #include #include 

                      函数原型

                      pid_t waitpid(pid_t pid, int *wstatus, int options);

                      函数参数:

                      pid : 进程 id

                      ​ -1 : 可以等待任意子进程

                      ​ >0 : 等待 id 为 pid 的进程

                      wstatus : 保存子进程退出状态值变量的指针

                      options : 选项

                      • 0:阻塞选项
                      • WNOHANG: 非阻塞选项

                        函数返回值

                        成功 :

                        • >0 : 退出进程的 pid
                        • = 0 : 在非阻塞模式下,没有进程退出

                          失败:

                          返回-1 并设置 errno

                          ​ 示例 : 创建一个子进程, 子进程运行后 3s 退出, 父进程等待子进程退出

                          #include#include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("the child process pid=%d\n",getpid());sleep(3);exit(66);}else if(pid>0){int status=0;//使用阻塞方式等待进程结束=wait(status)pid_t cpid=waitpid(-1,&status,0);if(cpid==-1){perror("[ERROR] wait():");exit(EXIT_FAILURE);}printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}return 0;}
                          #include#include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("the child process pid=%d\n",getpid());sleep(3);exit(66);}else if(pid>0){int status=0;pid_t cpid;//使用非阻塞方式,等待子进程结束后结束循环while((cpid=waitpid(-1,&status,WNOHANG))==0){}if(cpid==-1){perror("[ERROR] wait():");exit(EXIT_FAILURE);}printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}return 0;}

                          进程的替换

                          函数原型

                          int execl(const char *pathname, const char arg, … / (char *) NULL */);int execlp(const char *file, const chararg, … /(char *) NULL */);int execle(const char *pathname, const chararg, … /, (char *) NULL, char *const envp[] */);int execv(const char *pathname, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[], char *const envp[]);

                          函数参数

                          • pathname:可执行文件的路径名
                          • file : 可执行文件名,可以通过 path 环境变量指定的路径
                          • arg : 参数列表,以 NULL 结尾
                          • argv[] : 参数数组
                          • envp[] : 环境变量数组

                            函数返回值:

                            • 成功 : 0
                            • 失败 : -1

                              示例 : 通过 execl 函数族执行 ls -l 命令

                              #include#include#includeint main(){int result;result=execl("/bin/ls","ls","-l",NULL);if(result==-1){perror("[ERROR] execl:");}return 0;}

                              通过execv执行ls -l命令

                              #include#include#includeint main(){int result;char *argv[]={"ls","-l",NULL};//参数数组result=execv("/bin/ls",argv);if(result==-1){perror("[ERROR] execl:");}return 0;}

                              通过execvp执行ls -l命令

                              #include#include#includeint main(){int result;char *argv[]={"ls","-l",NULL};result=execvp("ls",argv);//只需要指定 可执行文件名,但是只能通过path环境可以找到的if(result==-1){perror("[ERROR] execl:");}return 0;}

                              进程间的通信

                              管道分为无名管道与有名管道

                              • 无名管道用于父子进程之间通讯
                              • 有名管道用于任意进程之间通讯

                                一、无名管道

                                无名管道的特点:

                                • 无名管道属于单向通讯
                                • 无名管道只能用于 父子进程通讯
                                • 无名管道发送端叫做写端, 接收端叫做读端
                                • 无名管道读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回读端与写端的文件描述符

                                  创建无名管道需要调用pipe()函数

                                  函数头文件

                                  #include 

                                  函数原型

                                  int pipe(int pipefd[2]);

                                  函数参数

                                  pipefd : 用于存储无名管道读端与写端的文件描述符的数组

                                  pipefd[0] : 读端文件描述符

                                  pipefd[1] : 写端文件描述符

                                  函数返回值:

                                  成功 : 0

                                  失败 :-1, 设置 errno

                                  代码示例:创建子进程,父进程通过管道向子进程发送 “Hello,pipe”

                                  #include#include#include#includeint main(){pid_t pid;int ret;int pipefd[2];ret=pipe(pipefd);//先创建管道,在fork进程if(ret==-1){perror("[ERROR] pipe():");exit(EXIT_FAILURE);}pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){ssize_t rbytes;char buffer[64]={0};close(pipefd[1]);//关闭写端//当管道为空时,读管道会阻塞读进程rbytes=read(pipefd[0],buffer,sizeof(buffer));if(rbytes==-1){perror("[ERROR] read():");exit(EXIT_FAILURE);}printf("%s\n",buffer);close(pipefd[0]);}else if(pid>0){close(pipefd[0]);//关闭读端ssize_t wbytes;char buffer[64]={"hello pipe"};wbytes=write(pipefd[1],buffer,sizeof(buffer));if(wbytes==-1){perror("[ERROR] write():");wait(NULL);//等待子进程结束close(pipefd[1]);exit(EXIT_FAILURE);}close(pipefd[1]);wait(NULL);}return 0;}

                                  当管道为空时,读管道会阻塞读进程

                                  当管道的写端被关闭了,从管道中读取剩余数据后,read 函数返回 0

                                  在写入管道时,确保不超过 PIPE_BUF 字节的操作是原子的

                                  当写入的数据达到 PIPE_BUF 字节时,write() 会在必要的时候阻塞直到管道中的可用空间足以原子地完成操作

                                  当写入的数据大于 PIPE_BUF 字节时,write() 会尽可能多传输数据以充满这个管道

                                  管道的大小是有限的, 不能让父/子进程同时对管道进行读/写操作

                                  当一个进程试图想一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符, 内核向写入进程发送一个 SIGPIPE 信号

                                  二、有名管道

                                  • 有名管道是在文件系统中可见的文件, 但是不占用磁盘空间, 仍然在内存中, 可以通过 mkfifo命令创建有名管道
                                  • 有名管道与无名管道一样,在应用层是基于文件接口进行操作
                                  • 有名管道用于任意进程之间的通讯, 当管道为空时, 读进程会阻塞.

                                    函数mkfifo()

                                    函数头文件

                                    #include #include 

                                    函数原型:

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

                                    函数参数:

                                    pathname : 有名管道路径名

                                    mode : 有名管道文件访问权限

                                    函数返回值:

                                    成功 : 返回 0

                                    失败 : 返回 -1, 并设置 errno

                                    创建两个没有血缘关系的进程,使用有名管道进行进程间通讯

                                    读程序

                                    #include#include#include#include#include#include#include#define FIFO_NAME "./fifo"int main(){int fd=open(FIFO_NAME,O_RDONLY);ssize_t rbytes;char buffer[64]={0};if(fd==-1){perror("[ERROR ]open():");exit(EXIT_FAILURE);}rbytes=read(fd,buffer,sizeof(buffer));if(rbytes==-1){perror("[ERROR] read():");exit(EXIT_FAILURE);}printf("%s\n",buffer);close(fd);return 0 ;}

                                    写程序

                                    #include#include#include#include#include#include#include#define FIFO_NAME "./fifo"int main(){int fd;ssize_t wbytes;char buffer[64]={"hello fifo"};mkfifo(FIFO_NAME,0644);fd=open(FIFO_NAME,O_WRONLY);if(fd==-1){perror("[ERROR] open():");exit(EXIT_FAILURE);}wbytes=write(fd,buffer,sizeof(buffer));if(wbytes==-1){perror("[ERROR] write:");exit(EXIT_FAILURE);}close(fd);return 0;}

                                    注意:

                                    如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式 (只写,读写)

                                    如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式 (只读,读写)

                                    有名管道的优缺点:

                                    优点:可以实现任意进程间通信(包括父子进程),操作起来和文件操作一样

                                    缺点:

                                    1. 打开的时候需要读写一起进行否则就会阻塞(如果读进程不读走管道 缓冲区中的数据,那么写操作将一直阻塞),管道大小是 4096 个字节
                                    2. 半双工的工作模式,如果和多个进程通信则需要创建多个管道

                                    三、信号

                                    在 Linux 系统可以通过

                                    kill -l

                                    命令查看, 常用的信号列举如下:

                                    SIGINT

                                    该信号在用户键入 INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程。

                                    SIGQUIT

                                    该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。

                                    SIGILL

                                    该信号在一个进程企图执行一条非法指令时 (可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时) 发出。

                                    SIGFPE

                                    该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数 > 为 0 等其它所有的算术的错误。

                                    SIGKILL

                                    该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。

                                    SIGALRM

                                    该信号当一个定时器到时的时候发出。

                                    SIGSTOP

                                    该信号用于暂停一个进程,且不能被阻塞、处理或忽略。

                                    SIGTSTP

                                    该信号用于交互停止进程,用户可键入 SUSP 字符时 (通常是 Ctrl-Z) 发出这个信号。

                                    SIGCHLD

                                    子进程改变状态时,父进程会收到这个信号

                                    SIGABRT

                                    进程异常中止

                                    当由进程来发送信号时, 则可以调用kill()函数与raise ()函数

                                    kill函数

                                    函数头文件

                                    #include #include 

                                    函数原型

                                    int kill(pid_t pid, int sig);

                                    函数功能:向指定的进程发送一个信号

                                    函数参数

                                    • pid : 进程的 id
                                    • sig : 信号的 id

                                      函数返回值:

                                      • 成功: 返回 0
                                      • 失败: 返回 -1, 并设置 errno
                                        raise函数

                                        函数头文件

                                        #include #include 

                                        函数原型

                                        int raise(int sig);

                                        函数参数:

                                        sig : 信号编号

                                        函数功能:允许进程向自身发送信号

                                        函数返回值

                                        • 成功 : 返回 0
                                        • 失败 : 返回 -1, 并设置 errno

                                          示例:创建一个子进程,子进程通过信号暂停,父进程发送 终止信号

                                          #include#include#include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("child process  is running pid=%d\n",getpid());raise(SIGSTOP);//给自己发送阻塞信号printf("child process is stop pid=%d\n",getpid());}else if(pid>0){int ret;sleep(1);ret=kill(pid,SIGKILL);//给子进程发送终止信号if(ret==0){printf("parent pid=%d kill child pid=%d\n",getpid(),pid);}wait(NULL);exit(EXIT_SUCCESS);}return 0;}
                                          pause() 函数

                                          功能:pause() 函数会让调用它的进程挂起(暂停执行),直到接收到一个信号。这意味着程序会一直暂停,直到有外部事件(如用户输入或者其他进程发送的信号)唤醒它。它没有参数,也不允许指定暂停的时间长度。

                                          头文件:

                                          #include 

                                          用法:

                                          pause(); // 程序暂停,等待信号唤醒

                                          返回值:接收到信号后,pause() 会返回接收到的信号的编号。如果没有信号到来,它就不会返回,也就是说正常情况下这个函数的调用不会返回到调用点

                                          #include#include#include#include#include#includeint main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("child process  is running pid=%d\n",getpid());pause();printf("child process is stop pid=%d\n",getpid());}else if(pid>0){int ret;sleep(3);ret=kill(pid,SIGUSR1);//SIGUSR1也是终止信号printf("parent pid=%d kill child pid=%d\n",getpid(),pid);wait(NULL);exit(EXIT_SUCCESS);}return 0;}
                                          自定义信号处理函数

                                          对于每种信号都有相应的默认处理方式

                                          进程退出:

                                          SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM

                                          进程忽略

                                          SIGCHLD,SIGPWR,SIGURG,SIGWINCH

                                          进程暂停

                                          SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

                                          通过signal函数设置信号处理方式

                                          函数头文件

                                          #include 

                                          函数原型

                                          sighandler_t signal(int signum, sighandler_t handler);

                                          函数功能:

                                          设置信号的处理方式, 如果是自定义处理方式,提供函数地址,注册到内核中

                                          函数参数

                                          signum : 信号编号

                                          函数返回值

                                          成功 : 返回信号处理函数地址

                                          失败 : 返回 SIG_ERR , 并设置 errno

                                          示例:创建一个子进程, 父进程给子进程发送 SIGUSR1 信号,并使用自定义的处理方式

                                          #include#include#include#include#include#include#includevoid do_signal_handler(int sig){printf("Receive signal %s\n",strsignal(sig));}int main(){pid_t pid;pid=fork();__sighandler_t h;h=signal(SIGUSR1,do_signal_handler);if(h==SIG_ERR){perror("[ERROR] signal():");exit(EXIT_FAILURE);}if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("child process  is running pid=%d\n",getpid());pause();printf("child process is stop pid=%d\n",getpid());}else if(pid>0){int ret;sleep(3);ret=kill(pid,SIGUSR1);printf("parent pid=%d kill child pid=%d\n",getpid(),pid);wait(NULL);exit(EXIT_SUCCESS);}return 0;}
                                          SIGALARM信号

                                          函数头文件

                                          #include 

                                          函数原型

                                          unsigned int alarm(unsigned int seconds);

                                          函数功能:设置定时器的秒数

                                          函数参数:

                                          seconds : 定时的时间秒数

                                          函数返回值:

                                          返回上一次进程设置定时器剩余的秒数

                                          要点:

                                          定时器的定时任务由内核完成, alarm 函数值负责设置定时时间, 并告诉内核启动定时器

                                          当定时时间超时后,内核会向进程发出 SIGALRM 信号

                                          #include#include#includeint main(){int ret;ret=alarm(5);printf("ret=%d\n",ret);//返回上一次进程设置定时器剩余的秒数sleep(2);ret=alarm(4);printf("ret=%d\n",ret);return 0;}

                                          打印结果:

                                          ret=0ret=3
                                          #include#include#include#include#includevoid do_handler_alarm(int sign){printf("Receice \n",strsignal(sign));}int main(){int ret;ret=alarm(5);__sighandler_t sigret;sigret=signal(SIGALRM,do_handler_alarm);pause();//等待计时器结束return 0;}
                                          子进程退出信号SIGCHLD

                                          问题:在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑

                                          #include#include#include#include#include#include#includevoid do_handler_chld(int sign){printf("Receice \n",strsignal(sign));wait(NULL);//不会阻塞,立即释放子进程的资源}int main(){pid_t pid;__sighandler_t sigret;sigret=signal(SIGCHLD,do_handler_chld);pid=fork();if(pid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else if(pid==0){printf("chlid is running pid=%d\n",getpid());sleep(2);exit(EXIT_SUCCESS);}else if(pid>0){while(1){}}return 0;}

                                          默认处理方式是忽略的

                                          四、消息队列

                                          System V IPC 对象共有三种

                                          • 消息队列
                                          • 共享内存
                                          • 信号量

                                            每个 IPC 对象都有一个唯一的 ID, 可以通过ftok()函数生成,ftok函数具体说明如下:

                                            函数头文件

                                            #include #include 

                                            函数原型

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

                                            函数参数

                                            • pathname : 文件路径名
                                            • proj_id : 8 bit 的 id 整数

                                              函数返回值:

                                              成功: 返回合成的 key(key 由 文件的 inode 节点号 与 proj_id 构成,inode 节点号 :每个存在的文件操作系统都会有唯一的编号, 通过 ls - i 命令查看)

                                              失败 : -1, 并设置 errno

                                              ls - i 命令查看inode节点号

                                              创建

                                              函数头文件

                                              #include #include #include 

                                              函数原型

                                              int msgget(key_t key, int msgflg);

                                              函数参数

                                              key : 由 ftok 函数合成

                                              msgflg : 消息队列标志

                                              • IPC_CREAT : 创建标志
                                              • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST

                                                权限控制标志

                                                函数返回值

                                                成功 : 返回 消息队列 id

                                                失败 : 返回 -1,并设置 errno

                                                #include#include#include#include#include#include#define PATH "."#define PRO_ID 88//8bit整数int main(){key_t key;int mid;key=ftok(PATH,PRO_ID);if(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}mid=msgget(key,IPC_CREAT|0644);if(mid==-1){perror("[ERROR] msgget():");exit(EXIT_FAILURE);}printf("message id=%d\n ",mid);return 0;}

                                                消息队列可以通过下面的命令查看

                                                ipcs -q

                                                删除消息队列

                                                ipcrm -q [msqid]
                                                删除

                                                删除消息队列需要调用msgctl函数, 具体信息如下

                                                函数头文件

                                                #include #include #include 

                                                函数原型

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

                                                函数参数

                                                msqid : 消息队列 id

                                                cmd : 命令字

                                                • IPC_STAT:获取消息队列属性
                                                • IPC_SET : 设置消息队列属性
                                                • IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

                                                  buf : 消息队列属性结构体对象指针

                                                  消息队列属性结构体定义如下:

                                                  struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};

                                                  在上一个示例的基础上,加上删除队列的代码

                                                  #include#include#include#include#include#include#define PATH "."#define PRO_ID 88//8bit整数int main(){key_t key;int mid;int ret;key=ftok(PATH,PRO_ID);if(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}mid=msgget(key,IPC_CREAT|0644);if(mid==-1){perror("[ERROR] msgget():");exit(EXIT_FAILURE);}printf("message id=%d\n ",mid);ret=msgctl(mid,IPC_RMID,NULL);if(ret==-1){perror("[ERROR] msgctl():");exit(EXIT_FAILURE);}return 0;}
                                                  发送

                                                  函数头文件

                                                  #include #include #include 

                                                  函数原型

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

                                                  函数参数:

                                                  1.msqid : 消息队列 ID

                                                  2.msgp : 消息结构体指针

                                                  3.msgsz : 消息内容的长度

                                                  4.msgflg : 消息队列标志,默认可以填 0

                                                  IPC_NOWAIT: 可以设置非阻塞

                                                  函数返回值

                                                  成功 : 返回 0

                                                  失败 : -1, 并设置 errno

                                                  消息结构定义形式如下:

                                                  struct msgbuf {//结构体名称自定义即可long mtype; /* message type, must be > 0 */char mtext[1]; /* message data */};
                                                  接收

                                                  函数头文件

                                                  #include #include #include 

                                                  函数原型

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

                                                  函数参数:

                                                  msqid : 消息队列 id

                                                  msgp : 消息结构指针

                                                  msgsz : 消息内容的长度

                                                  msgtyp : 消息类型

                                                  msgflg : 消息队列标志,默认可以填 0

                                                  IPC_NOWAIT: 可以设置非阻塞

                                                  函数返回值:

                                                  成功 : 返回实际读取消息内容的字节数

                                                  失败 : -1, 并设置 errno

                                                  示例: 创建两个没有血缘关系的进程, 使用 消息队列进行通讯

                                                  write_message.c

                                                  #include#include#include#include#include#include#define PATHNAME "."#define PRO_ID 10#define MSG_TYPE 100#define MSG_SZ 64struct msgbuf{long mtype;char mtext[MSG_SZ];};int main(){key_t key;key=ftok(PATHNAME,PRO_ID);int msg_id=msgget(key,IPC_CREAT|0666);if(msg_id==-1){perror("[ERROR] msgget():");exit(EXIT_FAILURE);}struct msgbuf message={MSG_TYPE,"hello msg queue"};int ret=msgsnd(msg_id,&message,strlen(message.mtext),0);if(ret==-1){perror("[ERROR] msgsnd():");exit(EXIT_FAILURE);}printf("message send success!");return 0;}

                                                  read_message.c

                                                  #include#include#include#include#include#include#include#define PATHNAME "."#define PRO_ID 10#define MSG_TYPE 100#define MSG_SZ 64struct msgbuf{long mtype;char mtext[MSG_SZ];};int main(){struct msgbuf message;ssize_t rbytes;key_t key;key=ftok(PATHNAME,PRO_ID);int msqid=msgget(key,IPC_CREAT|0666);//接收方也需要mesget一次if(msqid == -1){ perror("ftok(): "); exit(EXIT_FAILURE); }msgrcv(msqid,&message,MSG_SZ,MSG_TYPE,0);printf("Receice message %s\n types=%ld\n",message.mtext,message.mtype);return 0;}

                                                  五、共享内存

                                                  共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中, 减少数据在内核空间缓存

                                                  共享内存是一种效率较高的进程间通讯的方式

                                                  在 Linux 系统中通过 ipcs -m 查看所有的共享内存

                                                  创建

                                                  函数头文件

                                                  #include #include 

                                                  函数原型

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

                                                  函数功能

                                                  创建一个共享内存, 并返回 ID

                                                  函数参数

                                                  key : 由 ftok() 函数返回

                                                  size : 共享内存的大小

                                                  shmflg : 共享内存标志

                                                  • IPC_CREAT : 创建标志
                                                  • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST

                                                    权限控制标志

                                                    函数返回值

                                                    成功 : 返回 共享内存 id

                                                    失败 : 返回 -1, 并设置 errno

                                                    删除

                                                    函数头文件

                                                    #include #include 

                                                    函数原型

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

                                                    函数功能

                                                    1.共享内存控制函数, 功能由具体的功能命令字决定

                                                    2.函数参数

                                                    • shmid : 共享内存 id

                                                    • cmd : 控制命令字

                                                      ​ IPC_STAT: 获取 消息队列属性

                                                      ​ IPC_SET : 设置消息队列属性

                                                      ​ IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

                                                      3.buf : 共享内存属性结构体指针

                                                      消息队列属性结构体定义如下:

                                                      struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};

                                                      函数返回值

                                                      • 成功 : 返回 0 , 特殊命令字除外
                                                      • 失败 : 返回 -1

                                                        创建一个共享内存后,输出共享内存 id, 删除共享内存

                                                        #include#include#include#include#include#define PATHNAME "."#define PRO_ID 100#define SZ 256int main(){key_t key;key=ftok(PATHNAME,PRO_ID);if(key==-1){perror("[ERROR] ftok:");exit(EXIT_FAILURE);}int shmid=shmget(key,SZ,IPC_CREAT|0666);if(shmid==-1){perror("[ERROR] shmid():");exit(EXIT_FAILURE);}printf("shmid=%d\n",shmid);int ret=shmctl(shmid,IPC_RMID,NULL);if(ret==-1){  perror("[ERROR] shmctl():");    exit(EXIT_FAILURE);}return 0;}
                                                        映射

                                                        函数头文件

                                                        #include #include 

                                                        函数原型:

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

                                                        函数功能:将进程地址空间映射到共享内存上

                                                        函数参数:

                                                        shmid : 共享内存 id

                                                        shmaddr : 指定映射的到进程地址空间的起始地址,指定为 NULL 时, 由系统选择映射的地址

                                                        shmflg : 共享内存标志, 一般设置为 0

                                                        函数返回值:

                                                        成功 : 返回映射到进程地址空间的起始地址

                                                        失败 : (void *) -1, 并设置 errno

                                                        解除映射

                                                        函数头文件

                                                        #include #include 

                                                        函数原型

                                                        int shmdt(const void *shmaddr);

                                                        函数功能:解除进程地址空间与共享内存的映射

                                                        函数参数:

                                                        shm_addr: 由shmat返回的地址指针

                                                        函数返回值:

                                                        成功 : 返回 0

                                                        失败 : 返回 -1, 并设置 errno

                                                        示例:使用共享内存进行进程间通讯

                                                        share_write.c

                                                        #include#include#include#include#include#include#define PATHNAME "."#define PRO_ID 101#define SZ 256int main(){key_t key;int shmid,ret;void *addr=NULL;key=ftok(PATHNAME,PRO_ID);if(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}shmid=shmget(key,SZ,IPC_CREAT|0666);if(shmid==-1){perror("[ERROR] shmget():");exit(EXIT_FAILURE);}printf("shmid=%d",shmid);addr=shmat(shmid,NULL,0);if(addr==(void *)-1){perror("[ERROR] shmat():");exit(EXIT_FAILURE);}memset(addr,'A',10);//给addr放10个Ashmdt(addr);return 0;}

                                                        share_read.c

                                                        #include#include#include#include#include#include#define PATHNAME "."#define PRO_ID 101#define SZ 256int main(){key_t key;int shmid,ret;char buffer[10]={0};void *addr=NULL;key=ftok(PATHNAME,PRO_ID);if(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}shmid=shmget(key,SZ,IPC_CREAT|0666);if(shmid==-1){perror("[ERROR] shmget():");exit(EXIT_FAILURE);}addr=shmat(shmid,NULL,0);if(addr==(void *)-1){perror("[ERROR] shmat():");exit(EXIT_FAILURE);}memcpy(buffer,addr,10);printf("Receive %s\n",buffer);shmdt(addr);return 0;}

                                                        进程间同步

                                                        信号量

                                                        互斥: 同一时刻只有一个进程访问临界资源

                                                        同步: 在互斥的基础上增加了进程对临界资源的访问顺序

                                                        进程主要的同步与互斥手段是信号量

                                                        信号量: 由内核维护的整数, 其值被限制为大于或等于 0

                                                        信号可以执行如下操作:

                                                        将信号量设置成一个具体的值

                                                        在信号量当前值的基础上加上一个数值

                                                        在信号量当前值的基础上减上一个数值

                                                        等待信号量的值为 0

                                                        一般信号量分为 二值信号量 与 计数信号量

                                                        • 二值信号量: 一般指的是信号量 的值为 1, 可以理解为只对应一个资源
                                                        • 计数信号量: 一般指的是值大于等于 2 , 可以理解为对应多个资源

                                                          在 Linux 系统中查询信号量使用

                                                          ipcs -s
                                                          创建信号量集合

                                                          函数头文件

                                                          #include #include #include 

                                                          函数原型

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

                                                          函数功能:创建一个信号量集合

                                                          函数参数

                                                          key : 由 ftok() 函数生成

                                                          nsems : 信号量的数量

                                                          semflg : 信号量集合的标志

                                                          • IPC_CREAT : 创建标志
                                                          • IPC_EXCL : 与 IPC_CREAT 标志一起使用, 如果信号量集合存在就报错

                                                            权限标志

                                                            函数返回值

                                                            成功 : 返回信号量集合的 id

                                                            失败 : -1, 并设置 errno

                                                            设置信号量的数量

                                                            函数头文件

                                                            #include #include #include 

                                                            函数原型

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

                                                            函数功能:信号集合控制函数,根据 cmd 决定当前函数的功能

                                                            函数参数

                                                            • semid : 信号量集合的 id
                                                            • semnum : 信号量的编号,信号量的编号从 0 开始
                                                            • cmd : 命令控制字

                                                              SETVAL: 设置信号量的值

                                                              GETVAL: 获取信号量的值

                                                            • …: 后面是属于可变参参数列表, 根据不同的命令有不同的参数

                                                              信号量集合调用semctl函数,设置命令为IPC_RMID(删除信号量)时

                                                              注意 : 在使用 IPC_RMID 时,第 三个参数会被忽略

                                                              具体使用方法如下:

                                                              ret = semctl(semid,IPC_RMID,NULL);

                                                              函数返回值

                                                              成功 : 根据不同的命令有不同的返回值, 可以查看帮助文档关于 RETURN 的说明

                                                              GETNCNT the value of semncnt

                                                              GETPID the value of sempid

                                                              GETVAL the value of semval

                                                              GETZCNT the value of semzcnt.

                                                              All other cmd values return 0 on success.

                                                              失败 : 返回 -1, 并设置 errno

                                                              在使用命令时需要使用 union semun 共用体, 具体定义如下:

                                                              union semun {int val; /* Value for SETVAL */struct semid_ds   buf; /*Buffer for IPC_STAT, IPC_SET */unsigned short array; /*Array for GETALL, SETALL */struct seminfo__buf; /*Buffer for IPC_INFO(Linux-specific) */};

                                                              示例:创建一个信号量集合,集合中包含一个信号量, 并设置信号量的值为 1

                                                              #include#include#include#include#include#define SEM_PATHNAME "."#define SEM_PRO_ID 101union semun{int val;};int main(){key_t key=ftok(SEM_PATHNAME,SEM_PRO_ID);int sem_id,ret;union semun s;if(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}sem_id=semget(key,1,IPC_CREAT|0666);if(sem_id==-1){perror("[ERROR] semget():");exit(EXIT_FAILURE);}s.val=1;ret=semctl(sem_id,0,SETVAL,s);//初始化信号量的值,设置 第一个信号量的值为1if(ret==-1){perror("[ERROR] semctl():");exit(EXIT_FAILURE);}return 0;}
                                                              操作信号量

                                                              函数头文件

                                                              #include #include #include 

                                                              函数原型

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

                                                              函数功能:信号量操作函数,用于占用信号量、释放信号量、设置信号量等待

                                                              函数参数:

                                                              • semid : 信号量集合 id
                                                              • sops : 信号量操作结构体指针
                                                              • nsops : 操作的信号量的数量

                                                                函数返回值:

                                                                成功 : 返回 0

                                                                失败 : 返回 -1, 并设置 errno

                                                                struct sembuf 结构体

                                                                struct sembuf{unsigned short sem_num;//信号量编号, 从 0 开始short sem_op;/*信号量操作-1 : 占用资源+1 : 释放资源0 : 等待资源*/short sem_flg;/*信号量操作标志IPC_NOWAIT : 非阻塞,在信号量的值为 0 时, 会立即返回SEM_UNDO : 在进程终止时, 会自动释放信号量*/};

                                                                示例:使用信号量解决父子进程对终端的竞争

                                                                sem.h

                                                                #ifndef __SEM_H__#define __SEM_H__#include#include#include#include#include//创建信号量 nsems:信号量的个数,对应信号量的值valuesextern int sem_create(int nsems,unsigned short values[]);//占用信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2extern int sem_p(int semid,int semnum);//释放信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2extern int sem_v(int  semid,int semnum);//删除信号量集合extern int sem_del(int semid);#endif

                                                                sem.c

                                                                #include "sem.h"#define SEM_PATHNAME "."#define SEM_PRO_ID 102union semun{unsigned short *array;}; int sem_create(int nsems,unsigned short values[]){ key_t key; int sem_id,ret; union semun s; key=ftok(SEM_PATHNAME,SEM_PRO_ID);//生成key if(key==-1){ perror("[ERROR] ftok():"); exit(EXIT_FAILURE); } sem_id=semget(key,nsems,IPC_CREAT|0666);//创建信号量集合 if(sem_id==-1){ perror("[ERROR] semget():"); exit(EXIT_FAILURE); } s.array=values; ret=semctl(sem_id,0,SETALL,s);//按位初始化(使用SETALL时,第二位参数设置0) if(ret==-1){ perror("[ERROR] semctl():"); exit(EXIT_FAILURE); } return sem_id;  }int sem_p(int semid,int semnum){struct sembuf sops;sops.sem_num=semnum;sops.sem_op=-1;sops.sem_flg=SEM_UNDO;return semop(semid,&sops,1);}int sem_v(int  semid,int semnum){struct sembuf sops;sops.sem_num=semnum;sops.sem_op=1;sops.sem_flg=SEM_UNDO;return semop(semid,&sops,1);} int sem_del(int semid){ return semctl(semid,0,IPC_RMID,NULL); }

                                                                main.c

                                                                #include#include#include#include#include#include"sem.h"int main(){pid_t cpid;int semid;unsigned short values[]={1};semid=sem_create(1,values);cpid=fork();if(cpid==-1){perror("[ERROR] fork():");exit(EXIT_FAILURE);}else  if(cpid==0){while(1){sem_p(semid,0);printf("----------------\n");printf("C start\n");sleep(1);printf("C End \n");printf("-----------------\n");sem_v(semid,0);}}else if(cpid>0){sleep(1);while(1){sem_p(semid,0);printf("----------------\n");printf("P start\n");sleep(1);printf("P End \n");printf("-----------------\n");sem_v(semid,0);}}return 0;}

上一篇:Win10系统U盘制作全攻略,零基础教程,轻松上手一步到位!

下一篇:专线服务器端口怎么选择?