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

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 
#include 
#include 
#include 
#include 

#define SHARE_MEM_KEY 1234

int 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 
#include 
#include 
#include 
#include 

#define SHARE_MEM_KEY 1234

int 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 
#include 
#include 
#include 

#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 
#include 
#include 
#include   //包含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

  • http://blog.csdn.net/gx_1983/article/details/79435809

消息队列

  • 消息队列和命令管道一样,每个消息都有一个最大长度的限制。
  • 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 
#include 
#include 
#include 
#include 

#define MAX_TEXT 200

struct 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 
#include 
#include 
#include 
#include 

#define BUFSIZE 200

struct 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多线程应用中,每个线程都可以通过pthread_sigmask()来设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如SIGSEGV,另外不能被忽略处理的信号SIGKILL和SIGSTOP也无法被阻塞。
  • linux c库提供了两种线程的实现,一种是NPTL(Native POSIX Threads library,基于lnux 2.6内核实现,最普遍),另外一种是linux threads(已经过时)。基于NPTL的线程库,多线程应用中每个线程都有自己独特的线程ID,并共享同一个进程ID。应用程序可以通过调用kill(getpid(), signo)将信号发送到进程,如果进程中当前正在执行的线程没有阻碍此信号,则会被中断,信号处理函数会在此线程的上下文背景中执行。应用程序也可以通过调用pthread_kill(pthread_t thread, in sig)将信号发送给指定的线程,则线程处理函数会在指定的线程上下文背景中执行。
  • 信号的产生有以下几种方式:1. 用户从终端输入;2. 程序出错由内核产生;3. 程序运行逻辑需要,如调用kill()和raise()函数产生信号。
  • 编写安全的的信号处理函数的几个规则:
    • 信号处理函数尽量只执行简单的操作,譬如只是设置一个外部变量,其他复杂的操作留在信号处理函数之外执行。
    • 如果信号处理函数中调用了改变errno的函数,那么在信号处理函数执行之前最好保持旧的的状态,在信号处理函数执行结束之前要恢复旧的状态。
    • 信号处理函数只能调用可以重入的C库函数,譬如不能调用malloc(),free()以及标准IO库函数等。
    • 信号处理函数如果需要访问全局变量,那么在定义此全局变量时须要将其声明为volatile,以避免编译器不恰当的优化。
    • 从整个linux应用的角度出发,因为应用中使用了异步信号,程序中一些库函数在调用时可能被异步信号中断,此时必须根据errno的值考虑这些库函数调用被中断后的出错恢复处理。例如recv函数,参考下面的例子:
rlen = recv(sock_fd, buf, len, MSG_WAITALL);
if((rlen == -1) && (errno == EINTR)) {  
    //说明这个recv错误是由于recv函数被中断了引起的,我们可以把rlen设置为0,然后重新执行recv就可以了。
}
  • 在指定的线程中以同步的方式处理异步信号:
    • 这种在指定的线程中以同步方式处理信号的模型可以避免因为处理异步信号而给程序带来的不确定性和潜在危险。
  • 几个讲解linux信号机制比较好的连接

    • linux多线程应用中如何编写安全的信号处理函数
      • https://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/
    • 线程与信号处理函数获得同一把锁的死锁问题
      • http://www.cppblog.com/mysileng/archive/2013/01/05/196971.html
    • linux信号透彻分析与理解:
      • 链接
    • linux信号的阻塞和未决:
      • 链接
    • linux进程间通信-使用信号:
      • 链接
    • linux信号列表:
      • 链接
    • linux信号处理相关API:
      • 链接
  • 代码示例:

#include 
#include 
#include 

#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)是不同的。
 * */

信号量用于进程间的同步

  • 参考 https://www.cnblogs.com/shijingjing07/p/5615084.html
  • 参考 http://blog.csdn.net/usbdrivers/article/details/9110109
  • 信号量是一种特殊的变量访问具有原子性。
  • 有几种类型的信号量分别具有不同的功能:
    • SystemV信号量
      • 相关函数 semctl(), semget(), semop()
    • POSIX信号量
      • 匿名信号量
        • 相关函数 sem_getvalue(), sem_post(), sem_wait(), sem_trywait(), sem_timedwait(),
        • 仅供匿名信号量使用:sem_init(), sem_destroy()
      • 具名信号量
        • 比匿名信号量多个三个函数 sem_open(), sem_close(), sem_unlink()
  • 应用范围:
    • System V信号量常常用于进程的同步;
      • 生命周期:System V的生命周期是内核级别的,也就是说系统不发生重启,那么system v信号量一直有效。
    • POSIX信号量经常用于线程间的同步,也可以用于进程间的同步。
      • posix具名信号量一般用于进程间的同步,具名信号量是内核持续的。
      • posix匿名信号量一般用于线程同步,匿名信号量是进程持续的,也就是说进程消息信号量就无效了。

semget

  • 创建信号量。
  • int semget(key_t key, int num_sems, int sem_flags);
    • key:信号量键值,可以理解为信号量的唯一性标记。key_t为int。
    • num_sems:信号量的数目,一般为1
    • 返回值:相应的信号量标识符,失败返回-1
    • sem_flags:有两个值,IPC_CREATE和IPC_EXCL,
      • IPC_CREATE表示若信号量已存在,返回该信号量标识符。
      • IPC_EXCL表示若信号量已存在,返回错误。

setop

  • 操作信号量。
  • 参考 http://blog.csdn.net/wbj1234566/article/details/2256626

semctl

  • 用于信号量的初始化和删除
  • int semctl(int sem_id,int sem_num,int command,[union semun sem_union]);

你可能感兴趣的:(linux)