linux 内核向应用发出信号_linux 内核和应用层信号通信 - CSDN
精华内容
参与话题
  • 通过终端按键产生信号用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。SIGINT的默认处理动作是终止进程, SIGQUIT的默认处理...

    产生信号的条件主要有:

    A. 通过终端按键产生信号

    用户在终端按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SIGINT信 号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号。

    SIGINT的默认处理动作是终止进程, 
    SIGQUIT的默认处理动作是终止进程并且Core Dump,

    首先解释什么是Core Dump(核心转储)。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。

    进程异常终止通常是因为有 Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)。一个进程允许产生多大的core⽂文件取决于进程的 Resource Limit(这个信息保存 在PCB中)。默认是不允许产⽣生core文件的,因为core文件中 可能包含用户密码等敏感信息,不安全。

    在开发调试阶段可以用ulimit命令改变这个限制, 允许产生core文件。 
    首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:

    这里写图片描述

    test.c源代码:

    #include <stdio.h>
    
    int main()
    { 
    
        while(1);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    因为ulimit命令改变了Shell进程的Resource Limit, test进程的PCB由Shell进程复制⽽而来,所以 也具有和Shell进程相同的Resource Limit值,这样就可以产生Core文件了。 
    这里写图片描述

    进行调试test: 
    这里写图片描述

    B. 硬件异常产生信号

    这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为 SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核 将 这个异常解释为SIGSEGV信号发送给进程。

    file.c 源代码:

    #include <stdio.h>
    
    void handler(int data)
    { 
        printf("sig is %d\n", data);
    }
    
    int main()
    { 
        signal(11, handler);
        int ret = 1/0;
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里写图片描述

    C. 调用系统函数向进程发信号

    首先在后台执行死循环程序,然后⽤用kill命令给它发SIGSEGV信号。

    源代码:

     #include <stdio.h>
    
    int main()
    { 
    
        while(1);
    
        return 0;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里写图片描述

    2829是test进程的id。之所以要再次回车才显示Segmentation fault,是因为在2829进程终止掉 之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望 Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。 指定某种信号的kill命令可以有多种写法,上面的命令还可以写成kill -SIGSEGV 4568 或kill -11 4568, 11是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产 生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

    kill命令是调用kill函数实现的。 
    kill函数可以给一个指定的进程发送指定的信号。 
    raise函数可 以给当前进程发送指定的信号(自己给自己发信号)。

    #include <signal.h>
     int kill(pid_t pid, int signo); 
    int raise(int signo); 
    //这两个函数都是成功返回0,错误返回-1。
    • 1
    • 2
    • 3
    • 4

    源代码:

    #include <stdio.h>
    #include<signal.h>
    
    void handler(int data)
    { 
        printf("sig is %d\n", data);    
    }
    
    int main()
    { 
        signal(2, handler);
        sleep(3);
    
        while(1)
        { 
            raise(2);
            sleep(1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行结果图:

    这里写图片描述

    abort函数使当前进程接收到 信号而异常终止。

    #include <stdlib.h> 
    void abort(void); 
    //就像exit函数一样,abort函数总是会成功的,所以没有返回值。
    • 1
    • 2
    • 3

    运行结果图:

    pause()函数使该进程暂停让出CPU

    #include <unistd.h>
    
    int pause(void);
    • 1
    • 2
    • 3

    源代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    
    void sig_handler(int num)
    { 
    
        printf("receive the signal %d.\n", num);
        alarm(2);
    }
    
    int main()
    { 
    
        signal(SIGALRM, sig_handler);
        alarm(2);
        while(1){ 
    
            pause();
            printf("pause is over.\n");
        }
        exit(0);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果图: 
    这里写图片描述

    可以看出程序每隔2秒就会收到信号14,也就是SIGALRM信号;并且当处理完该信号之后,直接执行pause()函数下面的语句;说明pause()是可被中断的暂停;

    alarm函数 和SIGALRM信号

     #include <unistd.h>
      unsigned int alarm(unsigned int seconds);
    • 1
    • 2

    调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发 SIGALRM信号, 该信号的默认处理动作是终止当前进程。这个函数的返回值是0或者是以前 设定的闹钟时间还余下 的秒数。

    源代码:

    #include <stdio.h>
    
    
    int main()
    { 
        alarm(1);
        int count = 0;
    
        while(1)
        { 
            printf("count is %d\n", count);
            count++;
        }
    }
    //这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终⽌止
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果图: 
    这里写图片描述

    源代码:

    #include <stdio.h>
    #include <unistd.h>
    
    int count = 0;
    void handler(int data)
    { 
        printf("count is %d\n", count);
    }
    
    int main()
    { 
        signal(14, handler);
        alarm(1);
    
        while(1)
        { 
            count++;
        }
    
        return 0;
    }
    //这个函数遇上个函数作用相同,不同的是省去了I/O的输出的切换。
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里写图片描述

    可以看出,一秒钟比上一个代码计数多了好多倍,说明有输出的会延缓计数。












    【摘要】本文分析了Linux内核对于信号的实现机制和应用层 的相关处理。首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理。接着分析了内核对于信号的处理流程包括信号的触发/注册/执行 及注销等。最后介绍了应用层的相关处理,主要包括信号处理函数的安装、信号的发送、屏蔽阻塞等,最后给了几个简单的应用实例。

     

    【关键字】软中断信号,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t

     

    1       信号本质

    软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

     

    收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:

    第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。

    第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。

    第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

     

    2       信号的种类

    可以从两个不同的分类角度对信号进行分类:

    可靠性方面:可靠信号与不可靠信号;

    与时间的关系上:实时信号与非实时信号。

     

    2.1    可靠信号与不可靠信号

    Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。

     

    随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。

     

    信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在 支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送 函数kill()。

     

    信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。

     

    对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

     

    2.2    实时信号与非实时信号

    早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。

     

    非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

     

    3       信号处理流程

     

     

    对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个阶段:

    信号诞生

    信号在进程中注册

    信号的执行和注销

     

     

    3.1    信号诞生

    信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

     

    这里按发出信号的原因简单分类,以了解各种信号:

    (1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。

    (2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。

    (3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。

    (4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。

    (5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。

    (6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。

    (7) 跟踪进程执行的信号。

     

    Linux支持的信号列表如下。很多信号是与机器的体系结构相关的

    信号值 默认处理动作 发出信号的原因

    SIGHUP 1 A 终端挂起或者控制进程终止

    SIGINT 2 A 键盘中断(如break键被按下)

    SIGQUIT 3 C 键盘的退出键被按下

    SIGILL 4 C 非法指令

    SIGABRT 6 C 由abort(3)发出的退出指令

    SIGFPE 8 C 浮点异常

    SIGKILL 9 AEF Kill信号

    SIGSEGV 11 C 无效的内存引用

    SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道

    SIGALRM 14 A 由alarm(2)发出的信号

    SIGTERM 15 A 终止信号

    SIGUSR1 30,10,16 A 用户自定义信号1

    SIGUSR2 31,12,17 A 用户自定义信号2

    SIGCHLD 20,17,18 B 子进程结束信号

    SIGCONT 19,18,25 进程继续(曾被停止的进程)

    SIGSTOP 17,19,23 DEF 终止进程

    SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键

    SIGTTIN 21,21,26 D 后台进程企图从控制终端读

    SIGTTOU 22,22,27 D 后台进程企图从控制终端写

     

    处理动作一项中的字母含义如下

    A 缺省的动作是终止进程

    B 缺省的动作是忽略此信号,将该信号丢弃,不做处理

    C 缺省的动作是终止进程并进行内核映像转储(dump core),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员 提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。

    D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调用)

    E 信号不能被捕获

    F 信号不能被忽略

     

    3.2    信号在目标进程中注册

    在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。

     

    进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:

    struct sigpending{

            struct sigqueue *head, *tail;

            sigset_t signal;

    };

     

    第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

    struct sigqueue{

            struct sigqueue *next;

            siginfo_t info;

    }

     

    信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

     

    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信 号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配 一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。

     

    当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。

     

    总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)

     

     

    3.3    信号的执行和注销

    内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当其由于被信号唤醒或者正常调度重新获得CPU时,在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。

     

    对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程 未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的 数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完 毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。

     

    当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。

     

    内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

     

    处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。

     

    4       信号的安装

    如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。

     

    linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。

     

    4.1    signal()

    #include <signal.h>

    void (*signal(int signum, void (*handler))(int)))(int);

    如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:

    typedef void (*sighandler_t)(int);

    sighandler_t signal(int signum, sighandler_t handler));

    第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

    如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。

    传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。

     

    #include <signal.h>

    #include <unistd.h>

    #include <stdio.h>

    void sigroutine(int dunno)

    { /* 信号处理例程,其中dunno将会得到信号的值 */

            switch (dunno) {

            case 1:

            printf("Get a signal -- SIGHUP ");

            break;

            case 2:

            printf("Get a signal -- SIGINT ");

            break;

            case 3:

            printf("Get a signal -- SIGQUIT ");

            break;

            }

            return;

    }

     

    int main() {

            printf("process id is %d ",getpid());

            signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法

            signal(SIGINT, sigroutine);

            signal(SIGQUIT, sigroutine);

            for (;;) ;

    }

     

    其中信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-发出。该程序执行的结果如下:

     

    localhost:~$ ./sig_test

    process id is 463

    Get a signal -SIGINT //按下Ctrl-C得到的结果

    Get a signal -SIGQUIT //按下Ctrl-得到的结果

    //按下Ctrl-z将进程置于后台

     [1]+ Stopped ./sig_test

    localhost:~$ bg

     [1]+ ./sig_test &

    localhost:~$ kill -HUP 463 //向进程发送SIGHUP信号

    localhost:~$ Get a signal – SIGHUP

    kill -9 463 //向进程发送SIGKILL信号,终止进程

    localhost:~$

     

    4.2    sigaction()

    #include <signal.h>

    int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

    sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

     

    第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些信号等等。

    sigaction结构定义如下:

    struct sigaction {

                           union{

                                   __sighandler_t _sa_handler;

                                   void (*_sa_sigaction)(int,struct siginfo *, void *);

                           }_u

                sigset_t sa_mask;

                unsigned long sa_flags;

    }

     

    1、联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。

     

    2、由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用,第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:

    siginfo_t {

                      int      si_signo;  /* 信号值,对所有信号有意义*/

                      int      si_errno;  /* errno值,对所有信号有意义*/

                      int      si_code;   /* 信号产生的原因,对所有信号有意义*/

                                   union{                               /* 联合数据结构,不同成员适应不同信号 */

                                           //确保分配足够大的存储空间

                                           int _pad[SI_PAD_SIZE];

                                           //对SIGKILL有意义的结构

                                           struct{

                                                          ...

                                                     }...

                                                   ... ...

                                                   ... ...                               

                                           //对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构

                                      struct{

                                                          ...

                                                     }...

                                                   ... ...

                                             }

    }

     

    前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

     

    3、sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。

    注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

     

    4、sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。

     

     

    5       信号的发送

    发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

     

    5.1    kill()

    #include <sys/types.h>

    #include <signal.h>

    int kill(pid_t pid,int signo)

     

    该系统调用可以用来向任何进程或进程组发送任何信号。参数pid的值为信号的接收进程

    pid>0 进程ID为pid的进程

    pid=0 同一个进程组的进程

    pid<0 pid!=-1 进程组ID为 -pid的所有进程

    pid=-1 除发送进程自身外,所有进程ID大于1的进程

     

    Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

     

    Kill()最常用于pid>0时的信号发送。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应的错误代码errno。下面是一些可能返回的错误代码:

    EINVAL:指定的信号sig无效。

    ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。

    EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID 或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个组,则该错误表示组中有成员进程不能接收该信号。

     

    5.2    sigqueue()

    #include <sys/types.h>

    #include <signal.h>

    int sigqueue(pid_t pid, int sig, const union sigval val)

    调用成功返回 0;否则,返回 -1。

     

    sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

    sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

    typedef union sigval {

                   int  sival_int;

                   void *sival_ptr;

    }sigval_t;

     

    sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

     

    在调用sigqueue时,sigval_t指定的信息会拷贝到对应sig 注册的3参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

     

    5.3    alarm()

    #include <unistd.h>

    unsigned int alarm(unsigned int seconds)

    系统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。

     

    注意,在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。

     

    5.4    setitimer()

    现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,这两个调用的声明格式如下:

    int getitimer(int which, struct itimerval *value);

    int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);

    在使用这两个调用的进程中加入以下头文件:

    #include <sys/time.h>

     

    该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:

    TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。

    ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。

    ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。

     

    定时器中的参数value用来指明定时器的时间,其结构如下:

    struct itimerval {

            struct timeval it_interval; /* 下一次的取值 */

            struct timeval it_value; /* 本次的设定值 */

    };

     

    该结构中timeval结构定义如下:

    struct timeval {

            long tv_sec; /* 秒 */

            long tv_usec; /* 微秒,1秒 = 1000000 微秒*/

    };

     

    在setitimer 调用中,参数ovalue如果不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将it_value的值设 定为it_interval的值,然后重新开始计时,如此往复。当it_value设定为0时,计时器停止,或者当它计时到期,而it_interval 为0时停止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno:

    EFAULT:参数value或ovalue是无效的指针。

    EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。

    下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号:

     

    #include <signal.h>

    #include <unistd.h>

    #include <stdio.h>

    #include <sys/time.h>

    int sec;

     

    void sigroutine(int signo) {

            switch (signo) {

            case SIGALRM:

            printf("Catch a signal -- SIGALRM ");

            break;

            case SIGVTALRM:

            printf("Catch a signal -- SIGVTALRM ");

            break;

            }

            return;

    }

     

    int main()

    {

            struct itimerval value,ovalue,value2;

            sec = 5;

     

            printf("process id is %d ",getpid());

            signal(SIGALRM, sigroutine);

            signal(SIGVTALRM, sigroutine);

     

            value.it_value.tv_sec = 1;

            value.it_value.tv_usec = 0;

            value.it_interval.tv_sec = 1;

            value.it_interval.tv_usec = 0;

            setitimer(ITIMER_REAL, &value, &ovalue);

     

            value2.it_value.tv_sec = 0;

            value2.it_value.tv_usec = 500000;

            value2.it_interval.tv_sec = 0;

            value2.it_interval.tv_usec = 500000;

            setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

     

            for (;;) ;

    }

     

    该例子的屏幕拷贝如下:

    localhost:~$ ./timer_test

    process id is 579

    Catch a signal – SIGVTALRM

    Catch a signal – SIGALRM

    Catch a signal – SIGVTALRM

    Catch a signal – SIGVTALRM

    Catch a signal – SIGALRM

    Catch a signal –GVTALRM

     

    5.5    abort()

    #include <stdlib.h>

    void abort(void);

    向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

     

    5.6    raise()

    #include <signal.h>

    int raise(int signo)

    向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。

     

    6       信号集及信号集操作函数:

    信号集被定义为一种数据类型:

    typedef struct {

                           unsigned long sig[_NSIG_WORDS];

    } sigset_t

    信号集用来描述信号的集合,每个信号占用一位。Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

     

    #include <signal.h>

    int sigemptyset(sigset_t *set);

    int sigfillset(sigset_t *set);

    int sigaddset(sigset_t *set, int signum)

    int sigdelset(sigset_t *set, int signum);

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

    sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;

    sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;

    sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;

    sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;

    sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

     

    7       信号阻塞与信号未决:

    每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:

    #include <signal.h>

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

    int sigpending(sigset_t *set));

    int sigsuspend(const sigset_t *mask));

     

    sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

    SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号

    SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞

    SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

     

    sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

     

    sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

     

     

    8       信号应用实例

    linux下的信号应用并没有想象的那么恐怖,程序员所要做的最多只有三件事情:

    安装信号(推荐使用sigaction());

    实现三参数信号处理函数,handler(int signal,struct siginfo *info, void *);

    发送信号,推荐使用sigqueue()。

    实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。

     

    实例一:信号发送及处理

    实现一个信号接收程序sigreceive(其中信号安装由sigaction())。

    #include <signal.h>

    #include <sys/types.h>

    #include <unistd.h>

    void new_op(int,siginfo_t*,void*);

    int main(int argc,char**argv)

    {

            struct sigaction act;  

            int sig;

            sig=atoi(argv[1]);

           

            sigemptyset(&act.sa_mask);

            act.sa_flags=SA_SIGINFO;

            act.sa_sigaction=new_op;

           

            if(sigaction(sig,&act,NULL) < 0)

            {

                    printf("install sigal error\n");

            }

           

            while(1)

            {

                    sleep(2);

                    printf("wait for the signal\n");

            }

    }

     

    void new_op(int signum,siginfo_t *info,void *myact)

    {

            printf("receive signal %d", signum);

            sleep(5);

    }

    说明,命令行参数为信号值,后台运行sigreceive signo &,可获得该进程的ID,假设为pid,然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时,可验证信号的排队问题。

     

    实例二:信号传递附加信息

    主要包括两个实例:

    向进程本身发送信号,并传递指针参数

    #include <signal.h>

    #include <sys/types.h>

    #include <unistd.h>

    void new_op(int,siginfo_t*,void*);

    int main(int argc,char**argv)

    {

            struct sigaction act;  

            union sigval mysigval;

            int i;

            int sig;

            pid_t pid;         

            char data[10];

            memset(data,0,sizeof(data));

            for(i=0;i < 5;i++)

                    data[i]='2';

            mysigval.sival_ptr=data;

           

            sig=atoi(argv[1]);

            pid=getpid();

           

            sigemptyset(&act.sa_mask);

            act.sa_sigaction=new_op;//三参数信号处理函数

            act.sa_flags=SA_SIGINFO;//信息传递开关,允许传说参数信息给new_op

            if(sigaction(sig,&act,NULL) < 0)

            {

                    printf("install sigal error\n");

            }

            while(1)

            {

                    sleep(2);

                    printf("wait for the signal\n");

                    sigqueue(pid,sig,mysigval);//向本进程发送信号,并传递附加信息

            }

    }

    void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现

    {

            int i;

            for(i=0;i<10;i++)

            {

                    printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));

            }

            printf("handle signal %d over;",signum);

    }

     

    这个例子中,信号实现了附加信息的传递,信号究竟如何对这些信息进行处理则取决于具体的应用。

     

    不同进程间传递整型参数:

    把1中的信号发送和接收放在两个程序中,并且在发送过程中传递整型参数。

    信号接收程序:

    #include <signal.h>

    #include <sys/types.h>

    #include <unistd.h>

    void new_op(int,siginfo_t*,void*);

    int main(int argc,char**argv)

    {

            struct sigaction act;

            int sig;

            pid_t pid;         

           

            pid=getpid();

            sig=atoi(argv[1]);     

           

            sigemptyset(&act.sa_mask);

            act.sa_sigaction=new_op;

            act.sa_flags=SA_SIGINFO;

            if(sigaction(sig,&act,NULL)<0)

            {

                    printf("install sigal error\n");

            }

            while(1)

            {

                    sleep(2);

                    printf("wait for the signal\n");

            }

    }

    void new_op(int signum,siginfo_t *info,void *myact)

    {

            printf("the int value is %d \n",info->si_int);

    }

     

     

    信号发送程序:

    命令行第二个参数为信号值,第三个参数为接收进程ID。

     

    #include <signal.h>

    #include <sys/time.h>

    #include <unistd.h>

    #include <sys/types.h>

    main(int argc,char**argv)

    {

            pid_t pid;

            int signum;

            union sigval mysigval;

            signum=atoi(argv[1]);

            pid=(pid_t)atoi(argv[2]);

            mysigval.sival_int=8;//不代表具体含义,只用于说明问题

            if(sigqueue(pid,signum,mysigval)==-1)

                    printf("send error\n");

            sleep(2);

    }

     

     

    注:实例2的两个例子侧重点在于用信号来传递信息,目前关于在linux下通过信号传递信息的实例非常少,倒是Unix下有一些,但传递的基本上都是关于传递一个整数

     

    实例三:信号阻塞及信号集操作

    #include "signal.h"

    #include "unistd.h"

    static void my_op(int);

    main()

    {

            sigset_t new_mask,old_mask,pending_mask;

            struct sigaction act;

            sigemptyset(&act.sa_mask);

            act.sa_flags=SA_SIGINFO;

            act.sa_sigaction=(void*)my_op;

            if(sigaction(SIGRTMIN+10,&act,NULL))

                    printf("install signal SIGRTMIN+10 error\n");

            sigemptyset(&new_mask);

            sigaddset(&new_mask,SIGRTMIN+10);

            if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))

                    printf("block signal SIGRTMIN+10 error\n");

            sleep(10);

            printf("now begin to get pending mask and unblock SIGRTMIN+10\n");

            if(sigpending(&pending_mask)<0)

                    printf("get pending mask error\n");

            if(sigismember(&pending_mask,SIGRTMIN+10))

                    printf("signal SIGRTMIN+10 is pending\n");

            if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)

                    printf("unblock signal error\n");

            printf("signal unblocked\n");

            sleep(10);

    }

     

    static void my_op(int signum)

    {

            printf("receive signal %d \n",signum);

    }

     

    编译该程序,并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。

     

    9       参考鸣谢:

    linux信号处理机制(详解),http://www.zxbc.cn/html/20080712/61613.html

    Linux环境进程间通信(二): 信号(上),郑彦兴 (mlinux@163.com)

    signal、sigaction、kill等手册,最直接而可靠的参考资料。

    http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了许多系统调用、库函数等的在线指南。

    http://www.opengroup.org/onlinepubs/007904975/可以在这里对许多关键函数(包括系统调用)进行查询,非常好的一个网址

    进程间通信信号(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html

    进程间通信信号(下)http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html


    展开全文
  • linux alarm函数简介

    千次阅读 2017-06-24 10:01:19
    上代码: #include #include #include #include int main(int argc, char *argv[]) { alarm(5);...} 运行5秒后, 内核向进程发出SIGALRM信息, 进程被终止, 所以上述程序的结果是:Alarm c

            上代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <signal.h>
    
    int main(int argc, char *argv[]) 
    { 
    	alarm(5);
    	sleep(20); 
    	printf("end!\n"); 
    	return 0; 
    }
             运行5秒后, 内核向进程发出SIGALRM信息, 进程被终止, 所以上述程序的结果是:Alarm clock

             当然, 我们也可以人为定义信号处理函数, 如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <signal.h>
    
    void sig_alarm(int sig) 
    { 
    	printf("sig is %d, sig_alarm is called\n", sig);
    }
    
    int main(int argc, char *argv[]) 
    { 
    	signal(SIGALRM, sig_alarm); // 注册alarm信号对应的函数  
    	alarm(5);  // 5秒后,内核向进程发出alarm信号, 执行对应的信号注册函数
    	sleep(20); 
    	printf("end!\n"); 
    	return 0; 
    }
             结果:

    sig is 14, sig_alarm is called
    end!

            可以看到, 内核向应用进程发出SIGALRM信号, 执行对应的注册函数, 而非杀死进程。


            很简单, 先说这么多。



    展开全文
  • LINUX内核面试题---经典30道

    万次阅读 2013-02-28 15:49:23
    以下答案可能不是很详细。...从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡; 伴随着从非抢占内核到抢占内核的过度。L

    该链接有每个题详细的答案讲解http://www.docin.com/p-442803876.html

    以下答案可能不是很详细。

    1) Linux中主要有哪几种内核锁?

    Linux的同步机制从2.0到2.6以来不断发展完善。从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡;
    伴随着从非抢占内核到抢占内核的过度。Linux的锁机制越来越有效,也越来越复杂。
    Linux的内核锁主要是自旋锁和信号量。
    自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图请求一个已被争用(已经被持有)的自旋锁,那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。
    Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
    信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

    Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,另外一些同步机制,包括大内核锁、读写锁、大读者锁、RCU (Read-Copy Update,顾名思义就是读-拷贝修改),和顺序锁。
    2) Linux中的用户模式和内核模式是什么含意?

    MS-DOS等操作系统在单一的CPU模式下运行,但是一些类Unix的操作系统则使用了双模式,可以有效地实现时间共享。在Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内核本身处于内核模式以外,所有的用户进程都运行在用户模式之中。

    内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果用户模式的进程要享有此特权,它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。另外,用户模式的代码允许发生缺页,而内核模式的代码则不允许。

    在2.4和更早的内核中,仅仅用户模式的进程可以被上下文切换出局,由其他进程抢占。除非发生以下两种情况,否则内核模式代码可以一直独占CPU:

    (1) 它自愿放弃CPU;

    (2) 发生中断或异常。

    2.6内核引入了内核抢占,大多数内核模式的代码也可以被抢占。
    3) 怎样申请大块内核内存?

    在Linux内核环境下,申请大块内存的成功率随着系统运行时间的增加而减少,虽然可以通过vmalloc系列调用申请物理不连续但虚拟地址连续的内存,但毕竟其使用效率不高且在32位系统上vmalloc的内存地址空间有限。所以,一般的建议是在系统启动阶段申请大块内存,但是其成功的概率也只是比较高而已,而不是100%。如果程序真的比较在意这个申请的成功与否,只能退用“启动内存”(Boot Memory)。下面就是申请并导出启动内存的一段示例代码:

    void* x_bootmem = NULL;
    EXPORT_SYMBOL(x_bootmem);

    unsigned long x_bootmem_size = 0;
    EXPORT_SYMBOL(x_bootmem_size);

    static int __init x_bootmem_setup(char *str)
    {
    x_bootmem_size = memparse(str, &str);
    x_bootmem = alloc_bootmem(x_bootmem_size);
    printk(“Reserved %lu bytes from %p for x\n”, x_bootmem_size, x_bootmem);

    return 1;
    }
    __setup(“x-bootmem=”, x_bootmem_setup);

    可见其应用还是比较简单的,不过利弊总是共生的,它不可避免也有其自身的限制:

    内存申请代码只能连接进内核,不能在模块中使用。

    被申请的内存不会被页分配器和slab分配器所使用和统计,也就是说它处于系统的可见内存之外,即使在将来的某个地方你释放了它。

    一般用户只会申请一大块内存,如果需要在其上实现复杂的内存管理则需要自己实现。

    在不允许内存分配失败的场合,通过启动内存预留内存空间将是我们唯一的选择。
    4) 用户进程间通信主要哪几种方式?

    (1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。

    (2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

    (3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。

    (4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺

    (5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

    (6)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

    (7)套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
    5) 通过伙伴系统申请内核内存的函数有哪些?

    在物理页面管理上实现了基于区的伙伴系统(zone based buddy system)。对不同区的内存使用单独的伙伴系统(buddy system)管理,而且独立地监控空闲页。相应接口alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。
    6) 通过slab分配器申请内核内存的函数有?
    7) Linux的内核空间和用户空间是如何划分的(以32位系统为例)?
    8) vmalloc()申请的内存有什么特点?
    9) 用户程序使用malloc()申请到的内存空间在什么范围?

    10) 在支持并使能MMU的系统中,Linux内核和用户程序分别运行在物理地址模式还是虚拟地址模式?
    11) ARM处理器是通过几级也表进行存储空间映射的?
    12) Linux是通过什么组件来实现支持多种文件系通的?
    13) Linux虚拟文件系统的关键数据结构有哪些?(至少写出四个)

    struct super_block,struct inode,struct file,struct dentry;
    14) 对文件或设备的操作函数保存在那个数据结构中?

    struct file_operations
    15) Linux中的文件包括哪些?

    执行文件,普通文件,目录文件,链接文件和设备文件,管道文件。
    16) 创建进程的系统调用有那些?

    clone(),fork(),vfork();系统调用服务例程:sys_clone,sys_fork,sys_vfork;
    17) 调用schedule()进行进程切换的方式有几种?

    1.系统调用 do_fork();

    2.定时中断 do_timer();

    3.唤醒进程 wake_up_process

    4.改变进程的调度策略 setscheduler();

    5.系统调用礼让 sys_sched_yield();
    18) Linux调度程序是根据进程的动态优先级还是静态优先级来调度进程的?

    Liunx调度程序是根据根据进程的动态优先级来调度进程的,但是动态优先级又是根据静态优先级根据算法计算出来的,两者是两个相关联的值。因为高优先级的进程总是比低优先级的进程先被调度,为防止多个高优先级的进程占用CPU资源,导致其他进程不能占有CPU,所以引用动态优先级概念

    19) 进程调度的核心数据结构是哪个?

    struct runqueue
    20) 如何加载、卸载一个模块?

    insmod加载,rmmod卸载
    21) 模块和应用程序分别运行在什么空间?

    模块运行在内核空间,应用程序运行在用户空间
    22) Linux中的浮点运算由应用程序实现还是内核实现?

    应用程序实现,Linux中的浮点运算是利用数学库函数实现的,库函数能够被应用程序链接后调用,不能被内核链接调用。这些运算是在应用程序中运行的,然后再把结果反馈给系统。Linux内核如果一定要进行浮点运算,需要在建立内核时选上math-emu,使用软件模拟计算浮点运算,据说这样做的代价有两个:用户在安装驱动时需要重建内核,可能会影响到其他的应用程序,使得这些应用程序在做浮点运算的时候也使用math-emu,大大的降低了效率。
    23) 模块程序能否使用可链接的库函数?

    模块程序运行在内核空间,不能链接库函数。
    24) TLB中缓存的是什么内容?

    TLB,页表缓存,当线性地址被第一次转换成物理地址的时候,将线性地址和物理地址的对应放到TLB中,用于下次访问这个线性地址时,加快转换速度。
    25) Linux中有哪几种设备?

    字符设备和块设备。网卡是例外,他不直接与设备文件对应,mknod系统调用用来创建设备文件。
    26) 字符设备驱动程序的关键数据结构是哪个?

    字符设备描述符struct cdev,cdev_alloc()用于动态的分配cdev描述符,cdev_add()用于注册一个cdev描述符,cdev包含一个struct kobject 类型的数据结构它是核心的数据结构
    27) 设备驱动程序包括哪些功能函数?

    open(),read(),write(),llseek(),realse();
    28) 如何唯一标识一个设备?

    Linux使用一个设备编号来唯一的标示一个设备,设备编号分为:主设备号和次设备号,一般主设备号标示设备对应的驱动程序,次设备号对应设备文件指向的设备,在内核中使用dev_t来表示设备编号,一般它是32位长度,其中12位用于表示主设备号,20位用于表示次设备号,利用MKDEV(int major,int minor);用于生成一个dev_t类型的对象。
    29) Linux通过什么方式实现系统调用?

    靠软件中断实现的,首先,用户程序为系统调用设置参数,其中一个编号是系统调用编号,参数设置完成后,程序执行系统调用指令,x86上的软中断是有int产生的,这个指令会导致一个异常,产生一个事件,这个事件会导致处理器跳转到内核态并跳转到一个新的地址。并开始处理那里的异常处理程序,此时的异常处理就是系统调用程序。
    30) Linux软中断和工作队列的作用是什么?

     Linux中的软中断和工作队列是中断处理。

    1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个CPU上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。

    2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换。已完成不同的工作。

    可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。

    展开全文
  • 经典linux内核面试题及答案

    千次阅读 2016-05-25 11:49:12
    Linux 内核 面试题 答案

    1) Linux中主要有哪几种内核锁?

    2) Linux中的用户模式和内核模式是什么含意?

    3) 怎样申请大块内核内存?

    4) 用户进程间通信主要哪几种方式?

    5) 通过伙伴系统申请内核内存的函数有哪些?

    6) 通过slab分配器申请内核内存的函数有?

    7) Linux的内核空间和用户空间是如何划分的以32位系统为例)?

    8) vmalloc()申请的内存有什么特点?

    9) 用户程序使用malloc()申请到的内存空间在什么范围?

    10) 在支持并使能MMU的系统中,Linux内核和用户程序分别运行在物理地址模式还是虚拟地址模式?

    11) ARM处理器是通过几级也表进行存储空间映射的?

    12) Linux是通过什么组件来实现支持多种文件系通的?

    13) Linux虚拟文件系统的关键数据结构有哪些?至少写出四个)

    14) 对文件或设备的操作函数保存在那个数据结构中?

    15) Linux中的文件包括哪些?

    16) 创建进程的系统调用有那些?

    17) 调用schedule()进行进程切换的方式有几种?

    18) Linux调度程序是根据进程的动态优先级还是静态优先级来调度进程的?

    19) 进程调度的核心数据结构是哪个?

    20) 如何加载、卸载一个模块?

    21) 模块和应用程序分别运行在什么空间?

    22) Linux中的浮点运算由应用程序实现还是内核实现?

    23) 模块程序能否使用可链接的库函数?

    24) TLB中缓存的是什么内容?

    25) Linux中有哪几种设备?

    26) 字符设备驱动程序的关键数据结构是哪个?

    27) 设备驱动程序包括哪些功能函数?

    28) 如何唯一标识一个设备?

    29) Linux通过什么方式实现系统调用?

    30) Linux软中断和工作队列的作用是什么?

    1. Linux中主要有哪几种内核锁?

    Linux的同步机制从2.0到2.6以来不断发展完善。从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡;伴随着从非抢占内核到抢占内核的过度。Linux的锁机制越来越有效,也越来越复杂。

    Linux的内核锁主要是自旋锁和信号量。

    自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图请求一个已被争用已经被持有)的自旋锁,那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。

    Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。

    信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

    Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,另外一些同步机制,包括大内核锁、读写锁、大读者锁、RCU (Read-Copy Update,顾名思义就是读-拷贝修改),和顺序锁。

    2. Linux中的用户模式和内核模式是什么含意?

    MS-DOS等操作系统在单一的CPU模式下运行,但是一些类Unix的操作系统则使用了双模式,可以有效地实现时间共享。在Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内核本身处于内核模式以外,所有的用户进程都运行在用户模式之中。

    内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果用户模式的进程要享有此特权,它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。另外,用户模式的代码允许发生缺页,而内核模式的代码则不允许。

    在2.4和更早的内核中,仅仅用户模式的进程可以被上下文切换出局,由其他进程抢占。除非发生以下两种情况,否则内核模式代码可以一直独占CPU:

    (1) 它自愿放弃CPU;

    (2) 发生中断或异常。

    2.6内核引入了内核抢占,大多数内核模式的代码也可以被抢占。

    3. 怎样申请大块内核内存?

    在Linux内核环境下,申请大块内存的成功率随着系统运行时间的增加而减少,虽然可以通过vmalloc系列调用申请物理不连续但虚拟地址连续的内存,但毕竟其使用效率不高且在32位系统上vmalloc的内存地址空间有限。所以,一般的建议是在系统启动阶段申请大块内存,但是其成功的概率也只是比较高而已,而不是100%。如果程序真的比较在意这个申请的成功与否,只能退用“启动内存”Boot Memory)。下面就是申请并导出启动内存的一段示例代码:

    1. void* x_bootmem = NULL;
    2. EXPORT_SYMBOL(x_bootmem);
    3. unsigned long x_bootmem_size = 0;
    4. EXPORT_SYMBOL(x_bootmem_size);
    5. static int __init x_bootmem_setup(char *str)
    6. {
    7. x_bootmem_size = memparse(str, &str);
    8. x_bootmem = alloc_bootmem(x_bootmem_size);
    9. printk("Reserved %lu bytes from %p for x\n", x_bootmem_size, x_bootmem);
    10. return 1;
    11. }
    12. __setup("x-bootmem=", x_bootmem_setup);

    可见其应用还是比较简单的,不过利弊总是共生的,它不可避免也有其自身的限制:

    内存申请代码只能连接进内核,不能在模块中使用。被申请的内存不会被页分配器和slab分配器所使用和统计,也就是说它处于系统的可见内存之外,即使在将来的某个地方你释放了它。一般用户只会申请一大块内存,如果需要在其上实现复杂的内存管理则需要自己实现。在不允许内存分配失败的场合,通过启动内存预留内存空间将是我们唯一的选择。

    4. 用户进程间通信主要哪几种方式?

    1)管道Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。

    2)命名管道named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

    3)信号Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。

    4)消息Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺

    5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

    6)信号量semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

    7)套接字Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

    5. 通过伙伴系统申请内核内存的函数有哪些?

    在物理页面管理上实现了基于区的伙伴系统zone based buddy system)。对不同区的内存使用单独的伙伴系统(buddy system)管理,而且独立地监控空闲页。相应接口alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。

    补充知识:

    1.原理说明

    Linux内核中采 用了一种同时适用于32位和64位系统的内 存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表。

    * 页全局目录(Page Global Directory)

    * 页上级目录(Page Upper Directory)

    * 页中间目录(Page Middle Directory)

    * 页表(Page Table)

    页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指 向一个页框。Linux中采用4KB大小的 页框作为标准的内存分配单元。

    多级分页目录结构

    1.1.伙伴系统算法

    在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的 空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。

    为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个 块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连 续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。

    假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个 页框的链表中找,找到了则将页框块分为2个256个 页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页 框的链表查找,如果仍然没有,则返回错误。

    页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。

    1.2.slab分配器

    slab分配器源于 Solaris 2.4 的 分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。

    slab分配器为每种使用的内核对象建立单独的缓冲区。Linux 内核已经采用了伙伴系统管理物理内存页框,因此 slab分配器直接工作于伙伴系 统之上。每种缓冲区由多个 slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024个页框构成。出于对齐 等其它方面的要求,slab 中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的 内存浪费。

    2.常用内存分配函数

    2.1.__get_free_pages

    unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

    __get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函 数,从代码分析,alloc_pages函数会分配长度为1<

    2.2.kmem_cache_alloc

    struct kmem_cache *kmem_cache_create(const char *name, size_t size

    size_t align, unsigned long flags,

    void (*ctor)(void*, struct kmem_cache *, unsigned long),

    void (*dtor)(void*, struct kmem_cache *, unsigned long))

    void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)

    kmem_cache_create/ kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从 该高速缓存区域中获取新的内存块。kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏定义,在默认的2.6.18内核版本中,该宏定义为5,于是一次最多能申请1<<5 * 4KB也就是128KB的连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分配超过128KB的内存时使内核崩溃。

    2.3.kmalloc

    void *kmalloc(size_t size, gfp_t flags)

    kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函数来实现。kmalloc一次最多能申请的内存大小由include/linux/Kmalloc_size.h的 内容来决定,在默认的2.6.18内核版本中,kmalloc一次最多能申请大小为131702B也就是128KB字节的连续物理内存。测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过。

    2.4.vmalloc

    void *vmalloc(unsigned long size)

    前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也 可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表 示的是vmalloc分配的内存使用的地址范围。vmalloc对 一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中, 最大能一次分配1GB的空间。

    Linux内核部分内存分布

    2.5.dma_alloc_coherent

    void *dma_alloc_coherent(struct device *dev, size_t size,

    ma_addr_t *dma_handle, gfp_t gfp)

    DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU的参与,使用DMA机制能大幅提高与设备通信的 吞吐量。DMA操作中,涉及到CPU高速缓 存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很 好的解决了这个问题,dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函 数来分配内存,因此一次分配内存的大小限制和后者一样。__get_free_pages分配的内 存同样可以用于DMA操作。测试结果证明,dma_alloc_coherent函 数一次能分配的最大内存也为4M。

    2.6.ioremap

    void * ioremap (unsigned long offset, unsigned long size)

    ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段 物理地址映射到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存 分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。

    2.7.Boot Memory

    如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内 核引导阶段来预留部分内存。

    2.7.1.在内核引导时分配内存

    void* alloc_bootmem(unsigned long size)

    可以在Linux内核引导过程中绕过伙伴系统来分配大块内存。使用方法是在Linux内核引导时,调用mem_init函数之前 用alloc_bootmem函数申请指定大小的内存。如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导 出,然后就可以使用这块内存了。这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统 看不到这部分内存,需要用户自行管理。测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块。

    2.7.2.通过内核引导参数预留顶部内存

    在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内 存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。

    3.几种分配函数的比较

    分配原理最大内存其他

    __get_free_pages直接对页框进行操作4MB适用于分配较大量的连续物理内存

    kmem_cache_alloc基于slab机制实现128KB适合需要频繁申请释放相同大小内存块时使用

    kmalloc基于kmem_cache_alloc实现128KB最常见的分配方式,需要小于页框大小的内存时可以使用

    vmalloc建立非连续物理内存到虚拟地址的映射物理不连续,适合需要大内存,但是对地址连续性没有要求的场合

    dma_alloc_coherent基于__alloc_pages实现4MB适用于DMA操 作

    ioremap实现已知物理地址到虚拟地址的映射适用于物理地址已知的场合,如设备驱动

    alloc_bootmem在启动kernel时,预留一段内存,内核看不见小于物理内存大小,内存管理要求较高

    本文转自:http://www.bkjia.com/Linuxjc/859164.html

    以上30题完整答案:http://www.doc88.com/p-6803246858710.html

    展开全文
  • linux内核中的信号机制--信号发送

    千次阅读 2013-02-16 14:31:43
    linux内核中的信号机制--信号发送Kernel version:2.6.14CPU architecture:ARM920TAuthor:ce123(http://blog.csdn.net/ce123) 应用程序发送信号时,主要通过kill进行。注意:不要被“kill”迷惑,它并不是发送...
  • linux内核中的信号机制--一个简单的例子Author:ce123(http://blog.csdn.net/ce123) 信号机制是类UNIX系统中的一种重要的进程间通信手段之一。我们经常使用信号一个进程发送一个简短的消息。例如:假设我们启动...
  • Linux 信号signal处理机制

    万次阅读 2013-11-23 21:24:29
    信号Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念、Linux信号机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。 信号机制是进程之间相互传递消息的一种方法,信号全称为软...
  • Linux内核面试问题汇总

    千次阅读 2016-08-08 19:56:20
    1) Linux中主要有哪几种内核锁? Linux的同步机制从2.0到2.6以来不断发展完善。从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁。这些同步机制的发展伴随Linux从单处理器到对称多处理器的过渡; ...
  • 内核出的sigkill信号

    万次阅读 2013-02-22 17:54:42
    红帽Linux故障定位技术详解与实例是本文要介绍的内容,主要是来了解并学习红帽linux中故障定位技术的学习,故障定位技术分为在线故障定位和离线故障定位,一起来看详解。  红帽Linux故障定位技术详解与...
  • linux五种IO模型

    千次阅读 多人点赞 2018-07-01 23:13:54
     所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。比如,调用readfrom系统调用时,必须等待IO操作完成才返回。 异步  异步的概念和同步相对。当一个异步过程调用发出后,调用者不能...
  • linux的用户模式和内核模式

    千次阅读 2015-03-13 11:23:21
    Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内核本身处于内核模式以外,所有的用户进程都运行在用户模式之中。 内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/...
  • 目的:协议采样率收发离散度达到10us以下.. 问题:常规运行,为10~30毫秒,目前通过修改极限只能达到1000us.(5... Linux在实时方面存在的不足,Linux虽然符合POSIX1003.1b关于实时扩展部分的标准,例如:支持SCHED_
  • Linux内核的5个子系统

    万次阅读 2018-01-21 00:44:43
    首先一张熟悉的图来说明GNU/...Linux 内核可以进一步划分成 3 层:最上面是系统调用接口,用户程序通过软件中断后,调用系统内核提供的功能,这个在用户空间和内核提供的服务之间的接口称为系统调用,它实现了一些基
  • Linux内核组成部分(一)

    千次阅读 2019-01-11 10:31:38
    内核的任务 在纯技术层面上,内核是...例如,在内核寻址硬盘时,它必须确定使用哪个路径来从磁盘内存复制数据,数据的位置,经由哪个路径磁盘发送哪一条命令,等等。另一方面,应用程序只需发出传输数据的命令。...
  • Linux下利用signal函数处理ctrl+c等信号

    万次阅读 2014-07-13 17:53:16
    我们平时在程序运行的时候按下ctrl-c、ctrl-z或者kill一个进程的时候其实都等效于这个进程发送了一个特定信号,当进程捕获到信号后,进程会被中断并立即跳转到信号处理函数。默认情况下一个程序对ctrl-c发出信号...
  • Linux用户模式和内核模式

    千次阅读 2011-03-28 18:38:00
    MS-DOS等操作系统在单一的CPU模式下运行,...如果用户模式的进程要享有此特权,它必须通过系统调用设备驱动程序或其他内核模式的代码发出请求。另外,用户模式的代码允许发生缺页,而内核模式的代码则不允许。
  • 5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO

    万次阅读 多人点赞 2019-11-19 16:00:56
    本文是在《UNIX网络编程 卷1:套接字联网API》6.2节"I/O 模型 "的基础上,即UNIX/LINUX环境下的网络 IO环境下的理解,它里面给出的例子是读取(接收)网络UDP数据。下面简单写写自己对这些IO...
  • 浅析操作系统与操作系统内核

    千次阅读 多人点赞 2019-04-11 11:43:43
    最近看了一篇将Unix和Linux历史的帖子,虽然有点水,但是还是放个链接吧花了一天时间,终于把unix、linux、ios、android区别大致联系搞清楚,好像很复杂的,https://bbs.feng.com/read-htm-tid-6209622-page-1.html...
  • Linux用户态和内核态详解

    千次阅读 2018-07-11 19:06:12
    引述 当我们说“从用户态切换到内核态”时(例如在进行系统调用read或fork时),是指当前的进程从一种状态进入了另一种状态(并没有进程的切换)?还是指当前的进程被挂起了,另一种...
  • 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的创建 本章链接 链接地址 上一节 本章目录 下一节 CSDN 已是第一篇 无 ...
1 2 3 4 5 ... 20
收藏数 22,517
精华内容 9,006
关键字:

linux 内核向应用发出信号