linux/unix多线程/多进程编程总结(二)

来源:互联网 发布:王者荣耀 刘备 知乎 编辑:程序博客网 时间:2024/05/16 02:00

linuxUnix多线程多进程编程总结(一)

进程间通信

共享内存

  • 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。
  • 共享内存并没有提供同步机制,所以在使用的过程中需要与其他互斥机制配合使用,比如说互斥锁或者读写锁等。
  • 接口说明:
    • 接口说明:
接口说明:        int shmget(key_t key, size_t size, int shmflg); //创建共享内存                key: 共享内存id.                size: 共享内存大小.                shmflg: 权限标志,与文件权限相同.                返回值:成功返回0,失败返回-1.        void *shmat(int shm_id, const void *shm_addr, int shmflg); //连接共享内存                shm_id:共享内存Id.                shm_addr: 制定共享内存连接到当前进程中的位置,通常为空,表示让系统来选择共享内存的地址.                shm_flg: 标志位,通常为0                返回值:成功返回0,失败返回-1        int shmdt(const void *shmaddr); //分离共享内存,并不是删除,删除需要通过shmctl来操作.                shmaddr为shmat返回的共享内存位置指针。                返回值:成功返回0,失败返回-1        int shmctl(int shm_id, int command, struct shmid_ds *buf); //控制共享内存                shm_id:共享内存标识.                command: IPC_STAT, IPC_SET, IPC_RMID.                buf: buf为shmid_ds结构,用来存储要设置的共享内存参数,这个参数会传递给系统内核.
  • ipcs可以用来查询进程间通信对象(共享内存,消息队列,信号量等)的信息。
  • 代码说明:
    • 共享内存读取端代码:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/shm.h>#include <pthread.h>#define SHARE_MEM_KEY 1234int main(int argc, char ** argv) {        int shmId = 0; //共享内存标识        void * shm = NULL; //共享内存指针        pthread_mutex_t shm_mutex = PTHREAD_MUTEX_INITIALIZER;        shmId = shmget((key_t)SHARE_MEM_KEY, sizeof(int), 0666|IPC_CREAT);        if(shmId == -1) {                printf("shmget failed.\n");                exit(0);        }        printf("Memory attached at: %x\n", (int)shm);        //读共享内存        while(1) {                sleep(1);                int value = 0;                pthread_mutex_lock(&shm_mutex);                value = *(int *)shm;                pthread_mutex_unlock(&shm_mutex);                if(value != 0) {                        printf("The value is: %d\n", value);                }        }        return 0;}
 - 共享内存写入端代码:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/shm.h>#include <pthread.h>#define SHARE_MEM_KEY 1234int main(int argc, char ** argv) {        int i = 0;        int shmId = 0; //共享内存标识        void * shm = NULL; //共享内存指针        pthread_mutex_t shm_mutex = PTHREAD_MUTEX_INITIALIZER;        shmId = shmget((key_t)SHARE_MEM_KEY, sizeof(int), 0666|IPC_CREAT);        if(shmId == -1) {                printf("shmget failed.\n");                exit(0);        }        shm = shmat(shmId, 0, 0);        if(shm == (void*)-1) {                printf("shmat failed.\n");                exit(0);        }        printf("Memory attached at: %x\n", (int)shm);        //写共内存        while(1) {                sleep(1);                pthread_mutex_lock(&shm_mutex);                *(int *)shm = ++i;                pthread_mutex_unlock(&shm_mutex);                int value = *(int *)shm;                printf("Writting value:%d\n", value);        }        return 0;}

管道

有名管道

  • 有名管道可以用于没有亲缘关系的进程之间。
  • 有名管道API:
    int mkfifo(const char * pathname,mode_t mode);
    返回值:若成功则返回0,否则返回-1,错误原因存于errno中。
    pathname: 有名管道路径。
    mode: 文件打开方式,O_RDONLY,O_WRONLY, O_NONBLOCK等。
  • 注意:
    有名管道会在系统中形成一个管道文件,在程序退出的时候不会消失,下次程序启动之后可以重用。
    文件属性如下: prwxr-xr-x 1 root root 0 11月 17 13:47 p_fifo (第一位为p,代表pipe管道)
  • 代码示例:

reader代码:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#define P_FIFO "/tmp/p_fifo"int main( int argc, char ** argv ) {        int fd;        if(mkfifo(P_FIFO, 0777) < 0) {  //构建有名管道                printf("Create named pipe failed.\n");        }        fd = open(P_FIFO, O_RDONLY); //阻塞方式        while(1) {                char buf[100];                int count = 0;                memset(buf, 0, sizeof(buf));                count = read(fd, buf, 100); //因为以阻塞方式打开,所以没有数据会导致阻塞。                //一旦阻塞的read被触发后就不会再次被阻塞了。                //所以这里要判断read的返回值,当read返回0的时候代表读到了文件尾,此时buffer为空,就不需要打印。                if(count != 0) {                        printf("Count: %d, %s\n",count, buf);                }        }        close(fd);  //虽然不会走到这里,写在这做备忘。         return 0;}

writter代码:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>  //包含O_WRONLY O_RDONLY这些宏定义。#define P_FIFO "/tmp/p_fifo"int main(int argc, char ** argv) {        int i = 0;        int fd = open(P_FIFO, O_WRONLY); //非阻塞方式        //while(1) {        for(i = 0;i< 2;i++) {                write(fd, argv[1], 100);                printf(".");                sleep(1);        }        close(fd);        return 0;}

无名管道

socket

标准bsd socket

unix域socket

消息队列

  • 消息队列和命令管道一样,每个消息都有一个最大长度的限制。
  • linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
  • 消息队列函数:
    • msgget
int msgget(key_t key, int msgflag); //创建和访问一个消息队列。    msgflag是权限标志,表示消息队列的访问权限,它与文件的访问权>限一样。    msgflag | IPC_CREAT表示当key命令的消息队列不存在的时候创建一个消息队列。    如果key命令的消息队列存在,则返回这个消息>队列的标识符。失败时返回-1
  • msgsend
int msgsend(int msgid, const void * msg_ptr, size_t msg_sz, int msgflag);msgid:msgget返回的消息队列标识符。msg_ptr:消息结构指针。msg_sz:消息结构大小。成功返回0,失败返回-1
  • msgrcv
int msgrcv(int msgid, void * msg_ptr, size_t msg_st, long msgtype, int msgflag);msgid:消息队列id。msg_ptr: 消息buffer指针。msg_st: 消息buffer大小。msgtype:消息优先级,msgtype==0,获取队列的第一个消息;msgtype>0,获取消息队列中同等类型的消息。        msgtype<0,获取类型等于或者小于msgtype绝对值的第一个消息。成功返回读取的字节数,失败返回-1
  • msgctl
int msgctl(int msgid, int command, struct msgid_ds * buf);command可以去三个值:        IPC_STAT:获得消息状态;        IPC_SET:设置消息状态;        IPC_RMID:删除消息队列;成功返回0,失败返回-1
  • 代码示例:
    • 发送端代码
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/msg.h>#include <errno.h>#define MAX_TEXT 200struct msg_st {        long msg_type;        char text[MAX_TEXT];};int main(int argc, char ** argv) {        int running = 1;        char buffer[MAX_TEXT];        int msgid = -1;        struct msg_st data;        msgid = msgget((key_t)1234, 0666|IPC_CREAT);        if(msgid == -1) {                printf("Msgget failed with error:%d\n", errno);                exit(0);        }        while(running) {                printf("Enter some text:\n");                fgets(buffer, MAX_TEXT, stdin);                data.msg_type = 1;                strcpy(data.text, buffer);                if(msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1) {                        printf("Msgsnd failed.\n");                        exit(0);                }                if(strncmp(buffer, "end", 3) == 0) {                        printf("Setting running as 0.\n");                        running = 0;                }                sleep(1);        }        return 0;}
  • 接收端代码:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/msg.h>#include <errno.h>#define BUFSIZE 200struct msg_st {        long msg_type;        char text[BUFSIZE];};int main(int argc, char ** argv) {        int msgid = -1;        int running = 1;        struct msg_st data;        long msgtype = 0;        msgid = msgget((key_t)1234, 0666|IPC_CREAT); //创建消息队列        if(msgid == -1) {                printf("Msgget failed with error:%d\n", errno);                exit(0);        }        while(running) {                if(msgrcv(msgid, (void *)&data, BUFSIZ, msgtype, 0) == -1){                        printf("msgrcv failed with errno:%d\n", errno);                        exit(0);                }                printf("You wrote:%s\n", data.text);                if(strncmp(data.text, "end", 3) == 0) {                        printf("Received msg end.\n");                        running = 0;                }        }        if(msgctl(msgid, IPC_RMID, 0) == -1) {                printf("Msgctl error.\n");                exit(0);        }        return 0;}

信号

  • 信号阻塞和信号忽略:
    • 信号阻塞:操作系统在信号被进程解除组阻塞之前不会将信号传递出去,被阻塞的信号也不会影响进程的行为。信号只是暂时被阻止传递。当信号解除阻止的时候,进程还是能够收到信号的(如果在阻塞阶段有多个信号发送过来,那么对于不可靠信号,只保留最早的一个信号,后面的信号就被丢弃了;对于可靠信号,阻塞阶段的所有信号都会进入队列,然后解除阻塞之后进程能够收到所有信号。)
    • 信号忽略:当进程忽略一个信号的时候,信号会被丢弃,进程再也收不到这个信号了。
  • 一个进程的信号列表为一个64位的整数,每一个bit代表一个信号,其中1~31为不可靠信号,不可靠信号不支持排队; 34~63为可靠信号,可靠信号支持排队。
  • 信号的发送:
    • 可以通过C语言api发送信号,也可以从linux控制台通过kill命令发送信号。
  • 几个讲解linux信号机制比较好的连接

    • linux信号透彻分析与理解:
      • 链接
    • linux信号的阻塞和未决:
      • 链接
    • linux进程间通信-使用信号:
      • 链接
    • linux信号列表:
      • 链接
    • linux信号处理相关API:
      • 链接
  • 代码示例:

#include <stdio.h>#include <stdlib.h>#include <signal.h>#define TEST_SIGRTMIN 34//信号处理函数,函数原型 void function(int signo);void signal_handler(int signo) {        printf("====>signo %x\n", signo);        switch(signo) {                case TEST_SIGRTMIN:                        printf("signal SIGRTMIN.\n");                        break;                case SIGUSR2:                        printf("signal SIGUSR2.\n");                        break;                case SIGUSR1:                        printf("Signal SIGUSR1.\n");                        break;                case SIGINT:                        printf("====>SIGINT comes.\n");                        //signal(SIGINT, SIG_DFL);                        break;                default:                        printf("Receive signal number %d\n", signo);                        break;        }        //exit(0); //收到SIGINT信号之后程序退出。}int main(int argc, char ** argv) {        sigset_t initset;        int i;        sigemptyset(&initset);        sigaddset(&initset, SIGINT); //将SIGINT信号加入到信号集合中。        sigaddset(&initset, SIGUSR1); //把信号SIGUSR1加入到信号集合中。        sigaddset(&initset, SIGUSR2); //把信号SIGUSR2加入到信号集合中。        sigaddset(&initset, TEST_SIGRTMIN);  //实时信号,可以排队;非实时信号不能排队。        signal( SIGINT, SIG_IGN ); //忽略信号SIGINT        for(i = 0; i < 10; i++) {  //在此for循环期间输入终端信号,程序没有反应,不会退出。                sleep(1);                printf("Input crtl+C now. But this program will ignore it.\n");        }        signal(SIGINT, SIG_DFL); //对信号采用默认的处理方式        sigprocmask(SIG_BLOCK, &initset, NULL); //阻塞中断信号        for(i = 0;i < 20; i++) {                sleep(1);                printf("Input ctrl+C now.But the signal is blocked.\n"); //此时输入信号信号会阻塞。        }        signal(SIGINT, signal_handler);  //注册信号处理函数。        signal(SIGRTMIN, signal_handler);        sigprocmask(SIG_UNBLOCK, &initset, NULL); //设置信号为非阻塞,也就是从这之后系统开始接收中断信号了。        for(i = 0;i < 10; i++) {                sleep(1);                printf("Input ctrl+C now.The signal is handled by this program.\n");        }        return 0;}/* * 测试结果: *     SIG_IGN会使得信号被传递到程序,然后被程序忽略。 *     SIG_DFL使得程序按照默认的方式处理信号。 *     阻塞信号的时候内核会暂存程序的信号,信号在阻塞状态的时候不会被传递到程序,知道解除阻塞。 *     对与0~31的非可靠信号,解除阻塞之后在阻塞阶段发生的信号会被传递给程序,但是对于多个相同的信号只传递一次。 *     例如阻塞了三个信号SIGINT,SIGUSR1,SIGUSR2,如果在阻塞期间内,这几个信号到来,那么不会立即传递给应用程序,而是等到解除阻塞之后,最早的一个信号会被发送给程序,其余的信>号就被丢弃了,程序是感觉不到有后续信号发过来的。例如:在阻塞过程中发送的信号顺序为SIGUSR1,SIGINT,SIGUSR2,那么解除阻塞之后只有SIGUSR1会被程序处理,而程序根本感觉不到信号SIGINT和SIGUSR2。 *     对于可靠信号(kill -l:34~64),例如SIGRTMIN信号阻塞了,在阻塞期间如果有5个SIGRTMIN信号发送给程序,那么这5个信号会缓存起来,等待信号不再阻塞之后5个信号都能被程序接收到,这和不可靠信号(1~31)是不同的。 * */

信号量也可以用于进程间通信

  • 信号量参考信号量