精华内容
下载资源
问答
  • linux 线程间发送信号
    2019-02-26 02:49:00

    线程间通过 pthread_kill(thid,signo)给指定的thid线程发送signo信号。

    创建线程与线程屏蔽字顺序

     1. pthread_create();

         pthread_sigmask(); 线程创建在前。所以子线程没有继承主线程的接下来设置的屏蔽字。子线程依然可以响应主线程接下来要屏蔽的信号。也可以使用sigprocmask()屏蔽调用线程

        2.   pthread_sigmask();

         pthread_create();则两个线程拥有相同的屏蔽字。

     

    转载于:https://www.cnblogs.com/libing029/p/10434947.html

    更多相关内容
  •  Linux线程环境中的信号处理不同于进程的信号处理。一方面线程间信号处理函数的共享性使得信号处理更为复杂,另一方面普通异步信号又可转换为同步方式来简化处理。  本文首先介绍信号处理在进程中和线程间的...
  • linux中向某个线程发送信号,若没有对该信号的处理函数,则会导致程序结束。 如下面的程序,创建一个线程,主线程向其发送一个信号,会导致程序立即结束 #include #include <pthread> pthread_t t; void* run...
  • 在本篇文章里小编给大家整理的是关于linux线程间的同步与互斥的相关知识点,有兴趣的朋友们学习下。
  • linux线程间通信示例

    2021-05-14 01:27:50
    #include#include#includeintbuf[1000000];intw=0,r=0;//r是读指针,w是写指针intsize=10;//缓冲区大小pthread_mutex_tlock;//锁pthread_cond_tis_empty;...//缓冲区是否已满void*sender(void*a)//发送100个数据{in...

    d33f3cbd10e141b788e43a4f32bdccad.png

    #include

    #include

    #include

    intbuf[1000000];

    intw=0,r=0;//r是读指针,w是写指针

    intsize=10;//缓冲区大小

    pthread_mutex_tlock;//锁

    pthread_cond_tis_empty;//缓冲区是否为空

    pthread_cond_tis_full;//缓冲区是否已满

    void*sender(void*a)//发送100个数据

    {

    6acebbe2197e23d1e58b93a937ed9442.png

    inti,j,k;

    for(i=1;i

    {

    pthread_mutex_lock(&lock);//锁

    if(w-r>size)//缓冲区是否已满

    缺点是如果unlock和pthread_cond_signal之间,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(调用pthread_cond_wait的线程),而这在上面的放中间的模式下是不会出现的。缺点是如果unlock和pthread_cond_signal之间,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程 (调用pthread_cond_wait的线程)linux 多线程通信,而这在上面的放中间的模式下是不会出现的。mutex互斥锁必须是普通锁(pthread_mutex_timed_np)或者适应锁 (pthread_mutex_adaptive_np),且在调用pthread_cond_wait()前必须由本线程加锁 (pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并程挂起进入等待前解锁。

    buf[w++]=i;//缓冲区有空间,发送

    printf("%d--->/n",i);//打印出来

    读线程从共享资源中读数据,同时写线程需要向共享资源中写数据linux 多线程通信,当多个读、写线程共用一个资源(缓冲区)的时候,据需要使用诸如critical section或者互斥量的排他性锁(独占锁)来控制资源的访问,但是针对读操作较为频繁,写操作相对较少的情况下,使用独占锁是很不划算的,需要耗费更多的时间和其他资源。读线程从共享资源中读数据,同时写线程需要向共享资源中写数据,当多个读、写线程共用一个资源(缓冲区)的时候,据需要使用诸如criticalsection或者互斥量的排他性锁(独占锁)来控制资源的访问,但是针对读操作较为频繁,写操作相对较少的情况下,使用独占锁是很不划算的,需要耗费更多的时间和其他资源。对于每个工作线程,建立一个管道,fds[0]用于工作线程接收来自主进程的数据,fds[1]用于主进程向工作线程发送数据(这里的数据只做信号作用),thread_setup()则为每个事件创建一个独立的事件域t->base,并在t->base中注册了管道io事件,fds[0]的读事件,读事件触发则执行thread_libevent_process(),它负责完成由主进程转来的客户端连接conn。

    pthread_mutex_unlock(&lock);//解锁

    }

    552251c17aaa49632fc44a62e60fe939.png

    return;

    }

    void*receiver(void*a)//接受数据

    {

    intx;

    while(1)

    {

    pthread_mutex_lock(&lock);//锁

    if(r==w)//缓冲区是否有数据

    pthread_cond_wait(&is_empty,&lock);//没有数据,等待发送线程发数据

    x=buf[r++];//有数据了,取走

    13-810-jpg_6-1080-0-0-1080.jpg

    if(x!=0)

    printf("--->%d/n",x);//打印出来

    else

    break;//数据发送->接受完了,结束线程

    pthread_cond_signal(&is_full);//缓冲区至少还有一个空间可以让发送进程发送数据

    pthread_mutex_unlock(&lock);//解锁

    }

    return;

    }

    intmain()

    {

    ae4a5c7fc95f6b7dedabfb5c5d5c7162.gif

    memset(buf,0,sizeof(buf));

    pthread_ta,b;//创建两个线程

    //初始化

    pthread_mutex_init(&lock,NULL);

    pthread_cond_init(&is_full,NULL);

    pthread_cond_init(&is_empty,NULL);

    //线程开始工作

    pthread_create(&a,NULL,sender,0);

    pthread_create(&b,NULL,receiver,0);

    void*recycle;//线程结束时,存放回收的数据

    pthread_join(a,&recycle);//回收资源

    pthread_join(b,&recycle);//回收资源

    return0;

    }

    本文来自电脑杂谈,转载请注明本文网址:

    http://www.pc-fly.com/a/jisuanjixue/article-111970-1.html

    展开全文
  • [linux专题]基于linux线程信号处理

    千次阅读 2021-11-28 17:51:07
    3.linux进程通信

     

    目录

    1.linux线程

    1.1 基本介绍

    1.2 线程信息

    1.3 线程创建

    1.4 线程终止

    1.5 线程指定数据

    1.5 线程同步

    2.linux 信号与信号处理

    2.1 信号介绍

    2.2 信号种类

    2.3 信号处理

    2.4 信号发送

    2.5 信号屏蔽


    1.linux线程

    1.1 基本介绍

    线程是计算机独立运行的最小单位,也可以看出线程是系统分配CPU资源的基本单位,每个线程在系统给它时间片内,取得CPU的控制权,执行线程中的代码。

    相对于进程来说,线程创建消耗资源少,线程切换速度快,进程内的线程数据共享,通信效率高。

    1.2 线程信息

    线程ID,每个线程有唯一的线程ID

    寄存器,如PC计数器,堆栈指针等

    堆栈

    信号掩码

    优先级

    线程私有空间等。

    1.3 线程创建

    #include <pthread.h>
    
    typedef struct
    {
        int                   detachstate;     /*<!与进程脱离同步状态*/
        int                   schedpolicy;     /*<!线程调度策略*/
        struct sched_param    schedparam;      /*<!线程运行优先级等参数*/
        int                   inheritsched;    /*<!线程是否显示指定参数和调度策略*/
        int                   scope;           /*<!线程竞争CPU范围 (cpu内、进程内)*/
        size_t                guardsize;       /*<!警戒堆栈大小*/
        int                   stackaddr_set;   /*<!堆栈地址集*/
        void*                 stackaddr;       /*<!堆栈地址*/
        size_t                stacksize;       /*<!堆栈地址大小*/
    }pthread_attr_t;
    
    /* @desp, 创建线程
    *  @param,thread,创建返回的线程ID
    *  @param,attr,指定线程属性
    *  @param,start_routine,线程函数
    *  @param,arg,向线程函数传递的参数
    *  @return, 0,表示创建成功,否则失败
    */
    int pthread_create(pthread_t *thread,pthread_attr_t* attr,void* (*start_routine)(void*),void *arg);
    
    /* @desp, 获取线程Id
    *  @return,线程ID
    */
    pthread_t pthread_self(void);
    
    /* @desp, 比较两个线程ID是否指向同一线程
    *  @return,0 一样
    */
    int pthread_equal(pthread_t thread1,pthread_t thread2);
    
    /* @desp, 保证init_routine 线程函数在进程中执行一次
    *  @return,0执行成功
    */
    int pthread_once(pthread_once_t* once_control,void (*int_routine)(void));
    /*create_thread.c example*/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    
    int* thread_routine(void* arg);
    
    int main(void)
    {
        pthread_t thid;
    
        printf("main thread id is %u \r\n",pthread_self());
    
        if (pthread_create(&thid,NULL,(void*)thread_routine,NULL) != 0)
        {
            printf("thread create failed! \r\n");
            exit(1);
        }
        sleep(1);
        exit(0);
    }
    
    int* thread_routine(void* arg);
    {
        pthread_t thid;
    
        thid = pthread_self();
        printf("routine thread id is %u \r\n",thid);
    
        return NULL;
    }

    1.4 线程终止

    1> 从线程函数中,通过return返回终止;

    2> 调用pthread_exit() 终止线程。

    线程终止时的资源释放,如下成对出现的红函数,可以跟着线程的终止而释放资源

    #include <pthread.h>
    
    #define pthread_cleanup_push(routine,arg) \
        { struct _pthread_cleanup_buffer buffer; \
                 _pthread_cleanup_push(&buffer,(routine),(arg));
    #define pthread_cleanup_pop \
                 _pthread_cleanup_pop(&buffer,(routine));}

    线程间的同步等待

    #include <pthread.h>
    
    /*@desp,线程退出
    *       
    */
    void pthread_exit(void* ret);
    
    /*@desp,挂起自己,等待一个线程结束,
    *       一个线程仅仅允许一个线程等待终止
    */
    int pthread_join(pthread_t th,void* thread_ret);
    
    /*@desp,设置线程是否允许被同步
    *       
    */
    int pthread_detach(pthread_t th);

    1.5 线程指定数据

    线程指定数据(thread specific data TSD),通过键值访问方式,来达到线程私有数据目的。

    具体方法是,各个线程,使用公有的键来访问线程数据,而每个线程中使用该键对应的数据是不一致的,以此,达到私有数据访问的目的。

    #include <pthread.h>
    
    /*@desp,为线程,创建键值,私有数据*/
    int pthread_key_create(pthread_key_t *key,void (*destr_function)(void*));
    
    /*@desp,设置键值对应的私有数据*/
    int pthread_setspecific(pthread_key_t key,const void * ptr);
    
    /*@desp,获取键值对应的私有数据*/
    void* pthread_getspecific(pthread_key_t key);
    
    /*@desp,删除键值*/
    int pthread_key_delete(pthread_key_t key);

    1.5 线程同步

    同步的方式,主要包含,互斥锁,条件变量,异步信号。

    互斥锁

    #include <pthread.h>
    
    /*锁的属性
    * PTHREAD_MUTEX_TIMED_NP 普通锁,加锁后,形成等待队列
    * PTHREAD_MUTEX_RECURSIVE_NP 嵌套锁,允许同一线程多次加锁,多次解锁;
    * PTHREAD_MUTEX_ERRORCHECK_NP 检错锁,同一线程,请求同一个锁,返回EDEADLK;
    * PTHREAD_MUTEX_ADAPTIVE_NP 适应锁,解锁后,重新竞争
    */
    
    
    /*初始化锁*/
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    /*或初始化锁*/
    int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
    
    /*加锁,若已被锁,则阻塞,直到释放锁*/
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    /*或加锁,若已被锁,则立即返回busy*/
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    /*解锁*/
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    /*清除互斥锁,解锁后,才能清除成功*/
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    
    
    

    条件变量

    #include <pthread.h>
    
    /*初始化条件变量*/
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    /*或初始化条件变量*/
    int pthread_cond_init(pthread_cond_t 8cond,pthread_condattr_t *cond_attr);
    
    /*等待条件变量成立,否则阻塞*/
    int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
    
    /*等待条件变量成立,等待指定时间*/
    int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const struct timespec *abstime);
    
    /*激活条件,激活队列中靠前那一个*/
    int pthread_cond_signal(pthread_cond_t *cond);
    
    /*激活条件,激活所有队列*/
    int pthread_cond_broadcast(pthread_cond_t *cond);
    
    /*清除条件,只有没有等待时,才能清除*/
    int pthread_cond_destroy(pthread_cond_t *cond);
    

    异步信号

    #include <pthread.h>
    
    /*向特定线程,发送信号*/
    int pthread_kill(pthread_t threadid,int signo);
    
    /*设置信号掩码,如屏蔽或解除屏蔽等*/
    int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask);
    
    /*等待异步信号*/
    int sigwait(const sigset_t *set,int *sig);

    2.linux 信号与信号处理

    2.1 信号介绍

    信号是一种软中断,一种处理异步事件的方式,主要用来通知进程发生了事件,以及传递数据。

    信号的来源:终端按键,硬件异常,kill、sigqueue发送信号,alarm/timer等发送信号等;

    2.2 信号种类

    信号说明
    SIGHUP用户退出shell,该shell启动的进程收到该信号,默认为退出进程
    SIGINT用于按下 CTRL+C 发出该终端启动的程序该信号,默认为终止进程
    SIGQUIT用于按下 CTRL+\  向正在运行的程序发出该信号,默认终止进程,产生core文件
    SIGILLCPU 检测非法指令,终止进程,产生core文件
    SIGTRAP断点指令或trap指令产生,终止进程,产生core文件
    SIGABRT调用abort函数产生该信号,终止进程,产生core文件
    SIGBUS非法访问地址,内存地址对齐出错,终止进程,产生core文件
    SIGFPE致命的算术错误,如浮点运算错误,溢出,除0等,终止进程,产生core文件
    SIGKILL无条件终止进程,不可被忽略 处理和阻塞
    SIGUSR1用户定义信号
    SIGSEGV进程进行了无效的内存访问,终止进程,产生core文件
    SIGUSR2用户定义信号
    SIGPIPEbroken pipe,向一个没有读端的管道写数据
    SIGALRM定时器超时,系统调用alarm设置
    SIGTERM程序terminate信号,可被阻塞和处理,执行kill是产生这个信号
    SIGCHLD子进程结束时,父进程收到该信号,默认忽略该信号
    SIGCONT让一个暂停的进程继续执行
    SIGSTOPstop 进程的执行,只是让进程暂停,不可被忽略处理和阻塞
    SIGTSTP停止进程的运行,可被处理和忽略, 一般CTRL+Z发出,默认为暂停进程
    SIGTTIN后台进程从用户终端读数据,终端进程收到该信号,默认暂停进程
    SIGTTOU后台进程从用户终端写数据,终端进程收到该信号,默认暂停进程
    SIGURG套接字 socket有紧急数据时,向正在运行的进程发出该信号,默认动作为忽略
    SIGXCPU进程执行时间超过分配给该进程的CPU时间,默认为终止进程
    SIGXFSZ超过文件最大长度限制,终止进程,产生core文件
    SIGVTALRM虚拟时钟超时,默认动作终止进程
    SIGPROF同SIGVTALRM
    SIGWINCH窗口大小改变发出,默认忽略该信号
    SIGIO异步IO事件,默认忽略
    SIGPWR关机,默认终止进程
    SIGSYS无效的系统调用,终止进程,产生core文件
    SIGRTMIN~SIGRTMAX linux 实时信号,无固定意义
    1~31 不可靠信号33~64 可靠信号,不会丢失
    优先级实时信号,值越小,优先级越高

    2.3 信号处理

    #include <signal.h>
    
    typedef void (*sighandler_t)(int);
    
    struct sigaction
    {
        void (*sa_handler)(int);                       /*<! 信号处理函数*/
        void (*sa_sigaction)(int,siginfo_t*,void*);    /*<! 信号处理函数*/
        sigset_t sa_mask;                              /*<! 信号屏蔽码*/    
        int sa_flags;
        void (*sa_restorer)(void);                     /*<! 已作废*/
    };
    
    /*@desp,信号处理函数
    *@param,signum接收的信号
    *@param,handler信号处理函数
    */
    sighandler_t signal(int signum,sighandler_t handler);
    
    /*@desp,信号处理函数
    *@param,act 信号处理函数
    *@param,oldact 过去的信号处理函数
    */
    int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    
    int tmp = 0;
    void sigint_handler(int signo);
    
    int main(void)
    {
        struct sigaction act;
        
        act.sa_handler = sigint_handler;
        act.sa_flags = SA_NOMASK;
    
        sigaction(SIGINT,&act,NULL);
        
        while(1);
    
        exit(0);
    }
    
    void sigint_handler(int signo)
    {
        printf("recv SIGINT\n");
        sleep(5);
        tmp++;
        printf("tmp value is %d \n",tmp);
    }

    执行以上程序,按CTRL+C 出现效果,按CTRL+\ 退出

    #include <unistd.h>
    
    /*使调用进程挂起,直到捕获到信号*/
    int pause(void);

    2.4 信号发送

    kill 函数发送

    int kill(pid_t pid,int sig);

     pid>0 发送给指定pid信号

    pid=0,发送给进程组

    pid=-1 广播给除init和自身的所有进程

    pid < -1,进程组-pid的所有进程

    raise函数发送

    int raise(ing sig);

    sigqueue函数发送

    int sigqueue(pid_t pid,int sig,const union sigval var);

    支持带参数的发送信号 

    alarm函数发送

    unsigned int alarm(unsigned int seconds);

    按设定的时间发送SIGALRM信号

    abort函数发送

    void abort(void);

    发送SIGABRT信号

    2.5 信号屏蔽

    int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);

    how,主要包含如下设置

    SIG_BLOCK,设置为信号集并集 set| oldset

    SIG_SETMASK,设置为set

    int sigpending(sigset_t *set);

    获取当点pending的信号集

    int sigsuspend(const sigset_t *mask)

    将信号屏蔽码设置为mask,和pause一样,等待信号的发生并执行信号处理函数。

    展开全文
  • Linux线程信号

    千次阅读 2018-12-01 15:17:30
    1信号线程的关系 POSIX标准对多线程情况下的信号机制提出了一些要求: 信号处理函数必须在多线程进程的所有线程之间共享, 但是每个线程要有自己的挂起信号集合和阻塞信号掩码。... 如果发送一个致命信号到...

    1信号与线程的关系


    POSIX标准对多线程情况下的信号机制提出了一些要求:

    • 信号处理函数必须在多线程进程的所有线程之间共享, 但是每个线程要有自己的挂起信号集合和阻塞信号掩码。
    • POSIX函数kill/sigqueue必须面向进程, 而不是进程下的某个特定的线程。
    • 每个发给多线程应用的信号仅递送给一个线程, 这个线程是由内核从不会阻塞该信号的线程中随意选出来的。
    • 如果发送一个致命信号到多线程, 那么内核将杀死该应用的所有线程, 而不仅仅是接收信号的那个线程。

    这些就是POSIX标准提出的要求, Linux也要遵循这些要求, 那它是怎么做到的呢?


    1.1 线程之间共享信号处理函数


    对于进程下的多个线程来说, 信号处理函数是共享的。

    在Linux内核实现中, 同一个线程组里的所有线程都共享一个struct sighand结构体。 该结构体中存在一个action数组, 数组共64项, 每一个成员都是k_sigaction结构体类型, 一个k_sigaction结构体对应一个信号的信号处理函数。
    相关数据结构定义如下(这与架构相关, 这里给出的是x86_64位下的定义) :
     

    struct sigaction {__sighandler_t sa_handler;
    	unsigned long sa_flags;
    	__sigrestore_t sa_restorer;
    	sigset_t sa_mask;
    };
    struct k_sigaction {
    	struct sigaction sa;
    };
    struct sighand_struct {
    	atomic_t count;
    	struct k_sigaction action[_NSIG];
    	spinlock_t siglock;
    	wait_queue_head_t signalfd_wqh;
    };
    struct task_struct{
    	//...
    	struct sighand_struct *sighand;
    	//...
    }

    多线程的进程中, 信号处理函数相关的数据结构如图所示。
    内核中k_sigaction结构体的定义和glibc中sigaction函数中用到的struct sigaction结构体的定义几乎是一样的。 通过sigaction函数安装信号处理函数, 最终会影响到进程描述符中的sighand指针指向的sighand_struct结构体对应位置上的action成员变量。
    在创建线程时, 最终会执行内核的do_fork函数, 由do_fork函数走进copy_sighand来实现线程组内信号处理函数的共享。 创建线程时, CLONE_SIGHAND标志位是置位的。 创建线程组的主线程时, 内核会分配sighand_struct结构体; 创建线程组内的其他线程时, 并不会另起炉灶, 而是共享主线程的sighand_struct结构体, 只须增加引用计数而已。

     

    static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
    {
    	struct sighand_struct *sig;
    	if (clone_flags & CLONE_SIGHAND) {
    		//如果发现是线程, 则直接将引用计数++, 无须分配
    		sighand_struct结构
    		atomic_inc(&current->sighand->count);
    		return 0;
    	}
    	sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
    	rcu_assign_pointer(tsk->sighand, sig);
    	
    	if (!sig)
    		return -ENOMEM;
    	atomic_set(&sig->count, 1);
    	
    	memcpy(sig->action, current->sighand->action, sizeof(sig->action));
    	return 0;
    }

     

    2.2 线程有独立的阻塞信号掩码(信号集)


    每个线程都拥有独立的阻塞信号掩码。 在介绍这条性质之前, 首先需要介绍什么是阻塞信号掩码。


    就像我们开重要会议时要关闭手机一样, 进程在执行某些重要操作时, 不希望内核递送某些信号, 阻塞信号掩码就是用来实现该功能的。


    如果进程将某信号添加进了阻塞信号掩码, 纵然内核收到了该信号, 甚至该信号在挂起队列中已经存在了相当长的时间, 内核也不会将信号递送给进程, 直到进程解除对该信号的阻塞为止。


    开会时关闭手机是一种比较极端的例子。 更合理的做法是暂时屏蔽部分人的电话。 对于某些重要的电话, 比如儿子老师的电话、 父母的电话或老板的电话, 是不希望被屏蔽的。 信号也是如此。 进程在执行某些操作的时候, 可能只需要屏蔽一部分信号, 而不是所有信号。


    为了实现掩码的功能, Linux提供了一种新的数据结构: 信号集。 多个信号组成的集合被称为信号集, 其数据类型为sigset_t。 在Linux的实现中, sigset_t的类型是位掩码, 每一个比特代表一个信号。
    Linux提供了两个函数来初始化信号集, 如下:

    #include<signal.h>
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);

    sigemptyset函数用来初始化一个空的未包含任何信号的信号集, 而sigfillset函数则会初始化一个包含所有信号的信号集。


    注意

    必须要调用这两个初始化函数中的一个来初始化信号集, 对于声明了sigset_t类型的变量, 不能一厢情愿地假设它是空集合,也不能调用memset函数, 或者用赋值为0的方式来初始化。
    初始化信号之后, Linux提供了sigaddset函数向信号集中添加一个信号, 同时还提供了sigdelset函数在信号集中移除一个信号:

    int sigaddset(sigset_t *set, int signum);
    int sigdelset(sigset_t *set, int signum)

    为了判断某一个信号是否属于信号集, Linux提供了sigismember函数:

    int sigismember(const sigset_t *set, int signum);

    如果signum属于信号集, 则返回1, 否则返回0。 出错的时候, 返回-1。


    有了信号集, 就可以使用信号集来设置进程的阻塞信号掩码了。 Linux提供了sigprocmask函数来做这件事情:

    #include <signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    sigprocmask根据how的值, 提供了三种用于改变进程的阻塞信号掩码的方式, 见表6-14。

    注意

    我们知道SIGKILL信号和SIGSTOP信号不能阻塞, 可是如果调用sigprocmask函数时, 将SIGKILL信号和SIGSTOP信号添加进阻
    塞信号集中, 会怎么样?
    答案是不怎么样。 sigprocmask函数不会报错, 但是也不会将SIGKILL和SIGSTOP真的添加进阻塞信号集中。
    对应的rt_sigprocmask系统调用会执行如下语句, 剔除掉集合中的SIGKILL和SIGSTOP:

    sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));

    对于多线程的进程而言, 每一个线程都有自己的阻塞信号集:

    struct task_struct{
        sigset_t blocked;
    }

    sigprocmask函数改变的是调用线程的阻塞信号掩码, 而不是整个进程。 sigprocmask出现得比较早, 它出现在线程尚未引入Linux的时代。 在单线程的时代, 进程的阻塞信号掩码和线程的阻塞掩码是一回事, 但是引入多线程之后, sigprocmask的语义就变成了设置调用线程的阻塞信号掩码。
    为了更显式地设置线程的阻塞信号掩码, 线程库提供了pthread_sigmask函数来设置线程的阻塞信号掩码:

    #include <signal.h>
    int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

    事实上pthread_sigmask函数和sigprocmask函数的行为是一样的。
     

    1.3 私有挂起信号和共享挂起信号


    POSIX标准中有如下要求: 对于多线程的进程, kill和sigqueue发送的信号必须面对所有的线程, 而不是某个线程, 内核是如何做到的呢?而系统调用tkill和tgkill发送的信号, 又必须递送给进程下某个特定的线程。 内核又是如何做到的呢?
    前面简单提到过内核维护有挂起队列, 尚未递送进程的信号可以挂入挂起队列中。 有意思的是, 内核的进程描述符task_struct之中, 维护了两套sigpending, 代码如下所示:

    struct task_struct{
    	//...
    	struct signal_struct *signal;
    	struct sighand_struct *sighand;
    	struct sigpending pending;
    	//...
    }
    struct signal_struct {
    	//...
    	struct sigpending shared_pending;
    	//...
    }

     

    内核就是靠这两个挂起队列实现了POSIX标准的要求。 在Linux实现中, 线程作为独立的调度实体也有自己的进程描述符。 Linux下既可以向进程发送信号, 也可以向进程中的特定线程发送信号。 因此进程描述符中需要有两套sigpending结构。 其中task_struct结构体中的pending, 记录的是发送给线程的未决信号; 而通过signal指针指向signal_struct结构体的shared_pending, 记录的是发送给进程的未决信号。 每个线程都有自己的私有挂起队列(pending) , 但是进程里的所有线程都会共享一个公有的挂起队列(shared_pending) 。

    图6-4描述的是通过kill、 sigqueue、 tkill和tgkill发送信号后, 内核的相关处理流程。

    从图6-4中可以看出, 向进程发送信号也好, 向线程发送信号也罢, 最终都殊途同归, 在do_send_sig_info函数处会师。 尽管会师在一处, 却还是存在不同。 不同的地方在于, 到底将信号放入哪个挂起队列。
    在__send_signal函数中, 通过group入参的值来判断需要将信号放入哪个挂起队列(如果需要进队列的话) 。

    static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
    int group, int from_ancestor_ns)
    {
        //...
        pending = group ? &t->signal->shared_pending : &t->pending;
        //...
    }

    如果用户调用的是kill或sigqueue, 那么group就是1; 如果用户调用的是tkill或tgkill, 那么group参数就是0。 内核就是以此来区分该信号是发给进程的还是发给某个特定线程的, 如表6-15所示。

    上述情景并不难理解。 多线程的进程就像是一个班级, 进程下的每一个线程就像是班级的成员。 kill和sigqueue函数发送的信号是给进程
    的, 就像是优秀班集体的荣誉是颁发给整个班级的; tkill和tgkill发送的信号是给特定线程的, 就像是三好学生的荣誉是颁发给学生个的。

    另一个需要解决的问题是, 多线程情况下发送给进程的信号, 到底由哪个线程来负责处理? 这个问题就和高二(五) 班荣获优秀班集体,由谁负责上台领奖一样。


    内核是不是一定会将信号递送给进程的主线程?


    答案是不一定。 尽管如此, Linux还是采取了尽力而为的策略, 尽量地尊重函数调用者的意愿, 如果进程的主线程方便的话, 则优先选择主线程来处理信号; 如果主线程确实不方便, 那就有可能由线程组里的其他线程来负责处理信号。


    用户在调用kill/sigqueue函数之后, 内核最终会走到__send_signal函数。 在该函数的最后, 由complete_signal函数负责寻找合适的线程来处理该信号。 因为主线程的线程ID等于进程ID, 所以该函数会优先查询进程的主线程是否方便处理信号。 如果主线程不方便, 则会遍历线程组中的
    其他线程。 如果找到了方便处理信号的线程, 就调用signal_wake_up函数, 唤醒该线程去处理信号。

    signal_wake_up(t, sig == SIGKILL);

    如果线程组内全都不方便处理信号, complete函数也就当即返回了。
    如何判断方便不方便? 内核通过wants_signal函数来判断某个调度实体是否方便处理某信号:

    static inline int wants_signal(int sig, struct task_struct *p)
    {
    	if (sigismember(&p->blocked, sig))  /*位于阻塞信号集, 不方便*/
    		return 0;
    	if (p->flags & PF_EXITING)          /*正在退出, 不方便*/
    		return 0;
    	if (sig == SIGKILL)                /*SIGKILL信号, 必须处理*/
    		return 1;
    	if (task_is_stopped_or_traced(p))  /*被调试或被暂停, 不方便*/
    		return 0;
    	return task_curr(p) || !signal_pending(p);
    }

    glibc提供了一个API来获取当前线程的阻塞挂起信号, 如下:

    #include <signal.h>
    int sigpending(sigset_t *set);

    该函数很容易产生误解, 很多人认为该接口返回的是线程的挂起信号, 即还没有来得及处理的信号, 这种理解其实是错误的。
    严格来讲, 返回的信号集中的信号必须同时满足以下两个条件:

    • 处于挂起状态。
    • 信号属于线程的阻塞信号集。

    看下内核的do_sigpending函数的内容就不难理解sigpending函数的含义了:
     

    spin_lock_irq(&current->sighand->siglock);
    sigorsets(&pending, &current->pending.signal,&current->signal->shared_pending.signal);
    spin_unlock_irq(&current->sighand->siglock);
    sigandsets(&pending, &current->blocked, &pending);
    error = -EFAULT;
    if (!copy_to_user(set, &pending, sigsetsize))
    	error = 0;

    因此, 返回的挂起阻塞信号集合的计算方式是:
    1) 进程共享的挂起信号和线程私有的挂起信号取并集, 得到集合1。
    2) 对集合1和线程的阻塞信号集取交集, 以获得最终的结果。
    从此处可以看出, sigprocmask函数会影响到sigpendig函数的输出结果。
     

    1.4 致命信号下, 进程组全体退出


    关于进程的退出, 前面已经有所提及, Linux为了应对多线程, 提供了exit_group系统调用, 确保多
    个线程一起退出。 对于线程收到致命信号的这种情况, 操作是类似的。 可以通过给每个调度实体的
    pending上挂上一个SIGKILL信号以确保每个线程都会退出。 此处就不再赘述了。
     

     

     

     

    总结:

    • 信号处理函数是进程层面的概念, 或者说是线程组层面的概念, 线程组内所有线程共享对信号的处理函数。
    • 对于发送给进程的信号, 内核会任选一个线程来执行信号处理函数, 执行完后, 会将其从挂起信号队列中去除, 其他进程不会对一个信号重复响应。
    • 可以针对进程中的某个线程发送信号, 那么只有该线程能响应, 执行相应的信号处理函数。
    • 信号掩码是线程层面的概念, 信号处理函数在线程组内是统一的, 但是信号掩码是各自独立可配置的, 各个线程独立配置自己要阻止或放行的信号集合。
    • 挂起信号(内核已经收到, 但尚未递送给线程处理的信号) 既是针对进程的, 又是针对线程的。

    内核维护两个挂起信号队列, 一个是进程共享的挂起信号队列, 一个是线程特有的挂起信号队列。 调用函数sigpending返回的是两者的并集。 对于线程而言, 优先递送发给线程自身的信号。

     

    2.1 设置线程的信号掩码


    前面已提到过, 信号掩码是针对线程的, 每个线程都可以自行设置自己的信号掩码。 如果自己不设置, 就会继承创建者的信号掩码。
    NPTL实现了如下接口来设置线程的信号掩码:

    #include <signal.h>
    int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);

    how的值用来指定如何更改信号组:

    • SIG_BLOCK向当前信号掩码中添加new, 其中new表示要阻塞的信号组。
    • SIG_UNBLOCK从当前信号掩码中删除new, 其中new表示要取消阻塞的信号组。
    • SIG_SETMASK将当前信号掩码替换为new, 其中new表示新的信号掩码。

    该接口的使用方式和sigprocmask一模一样, 在Linux上, 两个函数的实现是相同的。


    说明 SIGCANCEL和SIGSETXID信号被用于NPTL实现, 因此用户不能也不应该改变这两个信号的行为方式。 好在用户不用操心这两个信号, sigprocmask函数和pthread_sigmask函数对这两者都做了特殊处理。
     

    2.2 向线程发送信号


    前面提到过向线程发送信号的系统调用tkill/tgkill, 无奈glibc并未将它们封装成可以直接调用的函数。 不过, 幸好提供了另外一个函数:

    int pthread_kill(pthread_t thread, int sig);

    由于pthread_t类型的线程ID只在线程组内是唯一的, 其他进程完全可能存在线程ID相同的线程, 所以pthread_kill只能向同一个进程的线程发送信号。
    除了这个接口外, Linux还提供了特有的函数将pthread_kill和sigqueue功能累加在一起:

    #define _GNU_SOURCE
    #include <pthread.h>
    int pthread_sigqueue(pthread_t thread, int sig,
                    const union sigval value);

    这个接口和sigqueue一样, 可以发送携带数据的信号。 当然, 只能发给同一个进程内的线程。
     

    2.3 多线程程序对信号的处理


    单线程的程序, 对信号的处理已经比较复杂了。 因为信号打断了进程的控制流, 所以信号处理函数只能调用异步信号安全的函数。 而异步信号安全是个很苛刻的条件。
    多线程的引入, 加剧了这种复杂度。 因为信号可以发送给进程, 也可以发送给进程内的某一线程。 不同线程还可以设置自己的掩码来实现对信号的屏蔽。 而且, 没有一个线程相关的函数是异步信号安全的, 信号处理函数不能调用任何pthread函数, 也不能通过条件变量来通知其他线程。
    正如陈硕在《Linux多线程服务器编程》 中提到的, 在多线程程序中, 使用信号的第一原则就是不要使用信号。

    • 不要主动使用信号作为进程间通信的手段, 收益和引入的风险完全不成比例。
    • 不主动改变异常处理信号的信号处理函数。 用于管道和socket的SIGPIPE可能是例外, 默认语义是终止进程, 很多情况下, 需要忽略该信号。
    • 如果无法避免, 必须要处理信号, 那么就采用sigwaitinfo或signalfd的方式同步处理信号, 减少异步处理带来的风险和引入bug的可能。


     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Linux进程通信-线程间通信

    千次阅读 2021-05-08 23:29:12
    Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程通信方法:管道、消息队列、共享内存、信号量、套接口。1、管道管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘...
  •  第一个参数为目标进程id,kill()可以向进程(或进程组),线程(轻权线程发送信号,因此pid有以下几种情况:  ● pid>0:目标进程(可能是轻权进程)由pid指定。  ● pid=0:信号被发送到当前进程组中的...
  • 条件变量是线程可用的另一种同步机制。...  满足条件给向进程发送信号 pthread_cond_signal  下面程序展示了利用条件变量等待另外两个线程满足条件时,第三个进程继续向前执行 #include #include #include p
  • 本文介绍信号的概念,signal()库函数,kill()库函数,可靠信号和不可靠信号的区别,通过一个例子来验证可靠与不可靠信号信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动,信号是...
  • 线程取消的方法是向目标线程发Cancel信号(pthread_cancel函数发送Cancel信号),但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Canc
  • 进程通信的概念 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2...
  • Linux系统中的进程通信方式 进程是一个独立的资源分配单元,不同的进程(通常指的是用户进程)之间的资源是独立的,没有关联,不能在同一个进程中直接访问另一个进程的资源。 进程通信(IPC)的目的: 数据...
  • Linux多线程3-4_向线程发送信号

    千次阅读 2019-12-26 21:10:11
    一、发送信号的函数 int pthread_kill(pthread_t thread, int sig); 1、别被名字吓到,pthread_kill可不是kill,而是向线程发送signal。还记得signal吗,大部分signal的默认动作是终止进程的运行,所以,我们才要...
  • 信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。...
  • 参考:https://blog.csdn.net/a987073381/article/details/52029070 https://blog.csdn.net/liu5320102/article/details/50764645 ...一、基础知识简介 1、 线程之间通信的...
  • 下面小编就为大家带来一篇Linux线程环境下 关于进程线程终止函数总结。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 信号量在进程中和在线程中有不同的接口实现,使用信号量来做多进程同步与使用信号量来做多线程间的同步是不一样的,这里将分别介绍信号量在多进程和多线程中的应用。 信号量在多进程中的应用: 这篇文章将讲述...
  • Linux线程同步信号发送与接收总结

    千次阅读 2017-11-27 17:32:23
    Linux中关于线程同步信号发送主要分为4步(默认有一个主线程和一个子线程): (1)首先包含头文件,再创建一个信号量,并初始化之:  #include &...(2)主线程发送信号:  sem_post(&amp;sem...
  • POSIX信号量详解
  • 1. linux下进程通信的几种主要手段简介: ... 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程通信外,进程还可以发送信号给进程本身;linux除了...
  • linux系统下建立多线程程序设计,完成UDP网络通信的发送与接收,包括总结与源代码,实测效果可见链接https://blog.csdn.net/zxp121127/article/details/78506081
  • 线程(11)多线程同步之信号量(Linux实现)
  • man 2 kill KILL(2) Linux Programmer's Manual KILL(2) NAME kill - send signal to a process //向进程发送信号 SYNOPSIS #include &
  • Linux高级编程——线程信号处理

    千次阅读 2017-09-27 12:08:04
    Linux线程环境中的信号处理不同于进程的信号处理。一方面线程间信号处理函数的共享性使得信号处理更为复杂,另一方面普通异步信号又可转换为同步方式来简化处理。  本文首先介绍信号处理在进程中和线程间的不同...
  • Linux线程

    千次阅读 多人点赞 2022-01-23 21:05:05
    文章目录Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程进程的多个线程共享关于进程线程的问题Linux线程控制POSIX线程库创建线程线程终止线程等待分离线程Linux线程互斥...
  • Linux线程-pthread_kill

    千次阅读 2021-05-14 19:22:12
    该函数可以用于向指定的线程发送信号:int pthread_kill(pthread_t threadId,int signal);如果线程内不对信号进行处理,则调用默认的处理程式,如SIGQUIT会退出终止线程,SIGKILL会杀死线程等等,可以调用signal...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,551
精华内容 25,020
关键字:

linux 线程间发送信号