linux多进程,文件描述符,锁,posix信号

文件

一般CPU都是以"页"为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,像INTEL的CPU,其一页在通常情况下是4086字节大小,而无论是数据段还是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开。系统在空间上的开销就可以达到最小

多进程中,每个进程维护一个文件描述表,指向系统维护的打开文件表,而打开文件表指向文件系统i-node表。打开文件表项记录着文件的读写位置。一般情况下,open操作新建一个文件描述表项和对应的打开文件表项。
特殊情况:

  • 同一进程打开同一文件两次,也会新建两个描述符和两个打开文件表项目。
  • 使用dup函数时,会新建描述符并指向旧的打开文件表项,并使打开文件表项的引用次数加1
  • 子进程会复制父进程的描述表,fork之后父子进程分别open,和多进程一般情况一样。
  • fork之前open, 各自的描述项指向同一打开文件表项,文件表项的引用次数加1
    需要注意的是:如果想要释放这个打开文件表项,也必须父子进程都close一次描述表项才会释放,如果不close,进程退出的时候会自动close掉所有的文件描述符。
    在这里插入图片描述

管道

基本用法:

  1. 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
  2. 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
  3. 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。

子进程会复制父进程的锁和它的锁状态,而子线程会直接操作主线程的锁,pthread提供借用专门的函数pthread_atfork,对fork()前后的锁状态进行清理

 #include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

pthread_atfork()在fork()之前调用,当调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。

posix信号

进程中的所有线程共享该进程的信号

kill -l //查看信号列表
kill -3 pid //给pid发送信号3

常见信号
信号编号 信号名 含义
1 HUP 挂起信号
2 INT 中断信号
3 QUIT 退出信号
9 KILL 杀死信号
11 SEGV 段错误信号
15 TERM 终止信号,kill命令默认发送的信号类型
18 CONT 继续运行信号,恢复之前接受了STOP信号的进程
19 STOP 暂停信号

int sigwait(const sigset_t * set,  int*  sig );

设置每个线程中需要等待的信号集合

int pthread_sigmask(int how, const sigset_t* newmask, sigset_t* oldmask)
  • 屏蔽信号集中的信号,如果在创建线程之前调用,新线程也会屏蔽相应的信号。
  • 如果想要子线程接收某信号则需要其它线程和主线程屏蔽该信号。
  • 通常做法是专门用一个子线程接收所有的信号

条件变量

在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),进入后自动解锁,而离开pthread_cond_wait()之前,mutex将被重新加锁。

在这里插入图片描述

  • 为什么有互斥锁还需要条件变量?

    /* 
     * Assume we have global variables:
     * int iCount == 0;
     * pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
     */
    
    //thread 1:
    
    while(true)
    {
          
          
        pthread_mutex_lock(&mutex);
        iCount++;
        pthread_mutex_unlock(&mutex);
    }
    
    
    //thread 2:
    while(true)
    {
          
          
        pthread_mutex_lock(&mutex);
        if(iCount >= 100)
        {
          
          
            iCount = 0;
        }
        pthread_mutex_unlock(&mutex);
    }
    

    在上面这个例子中,线程2必须不断通过重复获取锁,访问icount,解锁的方式来查看icount是否满足>=100的条件。而使用条件变量后,线程2只需要被挂起然后等待线程一发出条件满足的信号。
    使用条件变量后:

       //thread1 :
    while(true)
    {
          
          
        pthread_mutex_lock(&mutex);
        iCount++;
        pthread_mutex_unlock(&mutex);
    
        pthread_mutex_lock(&mutex);
        if(iCount >= 100)
        {
          
          
            pthread_cond_signal(&cond);
        }
        pthread_mutex_unlock(&mutex);
    }
    
    //thread2:
    while(1)
    {
          
          
        pthread_mutex_lock(&mutex);
        while(iCount < 100)
        {
          
          
            pthread_cond_wait(&cond, &mutex);
        }
        printf("iCount >= 100\r\n");
        iCount = 0;
        pthread_mutex_unlock(&mutex);
    }
    

    条件变量和互斥锁必须一起使用的原因是在thread 2 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread1 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。

如果信号没有线程接收的话就丢失了。

lock_guard 和unique_guard

lock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。
unique_guard除了lock_guard的功能外,提供了更多的member_function,相对来说更灵活一些。
unique_guard可以中途解锁,而lock_guard必须等到退出作用域。条件变量必须以unique_guard为参数。

猜你喜欢

转载自blog.csdn.net/weixin_39849839/article/details/109775567