linux第2天

孤儿进程和僵尸进程

 如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)

如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程。

孤儿进程

如果父亲进程先结束,子进程会托孤给1号进程

父进程注册SIGCHLD信号,然后在回调函数里while (waitpid(0, NULL, WNOHANG) > 0); 注意这个分号不能少.

避免僵尸进程的另一种方法

#include <signal.h>

signal(SIGCHLD, SIG_IGN); 告诉内核,本进程要忽略SIGCHLD,请内核大哥帮忙处理.

 

在SIGCHLD信号被显式忽略的情况下,2.6内核子进程将直接退出并释放资源,等父进程调用waitpid时,发现对应的pid进程已不存在,因而返回-1错误,errno为10(No child processes)。
从某位高手的博客里看到了这个解释。大概就是因为显示忽略SIGCHLD信号时,内核会处理子进程的退出状态,再调用waitpid的时候,就找不到子进程了。不显示忽略的时候,内核不会对子进程的退出做处理,所以还是能找到的。至于设置了信号处理函数之后为什么还是能正常返回?我推测应该是进程同步问题。信号处理函数对这个信号的处理是异步的,所以等到空闲时,父进程才会调用这个函数来处理信号,在这之前,waitpid就已经发挥作用,所以正常返回。由于初学,所以也不太懂,还请高手指正阿!

 

1、wait和waitpid出现的原因

SIGCHLD

  当子进程退出的时候,内核会向父进程发送SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)

  子进程退出时,内核将子进程置为僵尸状态,这个进程称为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态。

父进程查询子进程的退出状态可以用wait/waitpid函数

Wait获取status后检测处理

宏定义     描述

WIFEXITED(status)  如果子进程正常结束,返回一个非零值

  WEXITSTATUS(status)      如果WIFEXITED非零,返回子进程退出码

WIFSIGNALED(status)     子进程因为捕获信号而终止,返回非零值

  WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码

WIFSTOPPED(status)       如果子进程被暂停,返回一个非零值

  WSTOPSIG(status)   如果WIFSTOPPED非零,返回一个信号代码

 

用来等待某个特定进程的结束

waitpid(pid_t pid, int *status,int options)

对于waitpid的p I d参数的解释与其值有关:

 pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。

 pid > 0 等待其进程I D与p I d相等的子进程。

 pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用者进程同在一个组的进程。

 pid < -1 等待其组I D等于p I d的绝对值的任一子进程。

 

Wait和waitpid区别和联系

  在一个子进程终止前,wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞。

  waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。

  实际上wait函数是waitpid函数的一个特例。

 

abort() //会发出一个6号信号

 

会话期:是一个或者多个进程组的集合,通常一个会话期开始与用户登录,终止于用户退出。在此期间,该用户运行的所有进程都属于这个会话期。

 

 

 

 

中断

  中断是系统对于异步事件的响应()

  中断信号

  中断源

  现场信息

  中断处理程序

  中断向量表

异步事件的响应:进程执行代码的过程中可以随时被打断,然后去执行异常处理程序

生活中的中断和计算机系统中的中断

4)中断的其他概念

中断向量表保存了中断处理程序的入口地址。

中断个数固定,操作系统启动时初始化中断向量表。

中断有优先级(有人敲门,有人打电话,有优先级)

中断可以屏蔽(张三可以屏蔽电话)。

 

 

信号名称         描述

SIGABRT  进程停止运行 6

SIGALRM  警告钟

SIGFPE      算述运算例外

SIGHUP    系统挂断

SIGILL       非法指令

SIGINT      终端中断 2

SIGKILL     停止进程(此信号不能被忽略或捕获)

SIGPIPE    向没有读者的管道写入数据

SIGSEGV  无效内存段访问

 

SIGQUIT   终端退出3

SIGTERM 终止

SIGUSR1  用户定义信号1

SIGUSR2  用户定义信号2

SIGCHLD  子进程已经停止或退出

SIGCONT 如果被停止则继续执行

SIGSTOP   停止执行

SIGTSTP   终端停止信号

SIGTOUT  后台进程请求进行写操作

SIGTTIN   后台进程请求进行读操作

 

进程对信号的三种相应

  忽略信号

         不采取任何操作、有两个信号不能被忽略:SIGKILL(9号信号)和SIGSTOP。

思考1:为什么进程不能忽略SIGKILL、SIGSTOP信号。(如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理。)。SIGKILL(9号信号)和SIGSTOP信号是不能被捕获的。

  捕获并处理信号

         内核中断正在执行的代码,转去执行先前注册过的处理程序。

  执行默认操作

         默认操作通常是终止进程,这取决于被发送的信号。

 

 

signal信号安装函数

函数原型:__sighandler_t signal(intsignum, __sighandler_t handler);

返回值,如果成功则返回信号以前的行为

 

不可靠信号PK可靠信号

不可靠信号是前32个信号,不支持排队.在快速发送时可能会造成丢失,因为在进程PCB控制的信号未决信号集,是不能排队的,多个同种信号的到来,只会有一个同种信号在未决集.

可靠信号

  随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

 

sleep函数几点说明

1)sleep函数作用,让进程睡眠。

2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码

3)sleep函数的返回值,是剩余的秒数

 

raise

  raise

  给自己发送信号。raise(sig)等价于kill(getpid(), sig);

  killpg

 给进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);

  sigqueue

  给进程发送信号,支持排队,可以附带信息。

 

pause()函数

  将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使linux进程调度器找到另一个进程来运行。

  pause使调用者进程挂起,直到一个信号被捕获

 

alarm函数,设置一个闹钟延迟发送信号

告诉linux内核n秒中以后,发送SIGALRM信号;;

 

 

可重入函数概念

  为了增强程序的稳定性,在信号处理函数中应使用可重入函数。

  所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。

满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的

 

 

信号在内核中的表示

  执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。

进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

画图

linux第2天_第1张图片

当一个信号发送给一个进程时,进程控制块pcb控制着这个信号的走向,未决集(只可读,不可写),阻塞集(可读可写)都是64位数,因为有64个信号,每个位对应一个信号,一个信号要通过这两个集才能抵达

未决集初始状态全为0,信号首先进入到未决集,未决集的相应位变成1,然后查看阻塞位,如果阻塞位是1,那么信号无法通过,就一直在未决集,此时叫做未决态,一旦阻塞位变成0了,信号马上通过,未决集还没有变成0,

信号最后由程序决定如何处理(忽略,捕捉,默认行为),处理时未决集变成0,可以接收新的信号.

 

在信号处理函数中对某个信号进行解除阻塞时,则只是将pending位清0

 

信号集操作函数(状态字表示)

  #include <signal.h>

  intsigemptyset(sigset_t *set);把信号集清空 64bit/8=8个字节   sigset_t是一个64位的数

  intsigfillset(sigset_t *set);把信号集全填写成1

  intsigaddset(sigset_t *set, intsigno);根据signo,把信号集中的对应为置成1

  intsigdelset(sigset_t *set, intsigno);根据signo,把信号集中的对应为置成0

intsigismember(constsigset_t *set, intsigno);//判断signo是否在信号集中

 

sigprocmask读取或更改进程的信号屏蔽状态字(block)

  #include <signal.h>

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

  功能:读取或更改进程的信号屏蔽字。

  返回值:若成功则为0,若出错则为-1

 

sigpending获取信号未决状态字(pending)信息

   #include <signal.h>

int sigpending(sigset_t *set);

 

sigaction函数

信号注册函数,与signal一样,只不过功能更加强大.

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

sigaction结构体

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

structsigaction {

         void (*sa_handler)(int);   //信号处理程序不接受额外数据

         void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序能接受额外数据,和sigqueue配合使用

         sigset_t sa_mask; //信号处理函数执行的过程中阻塞哪些信号 sa_mask类型与未注册的信号集一样.但不需要sigprocmask

         int sa_flags; //影响信号的行为SA_SIGINFO表示能接受数据

         void (*sa_restorer)(void); //废弃

};

注意1:回调函数句柄sa_handler、sa_sigaction只能任选其一。

 

 

sigqueue新的信号发送函数,功能更强大.

    struct sigaction act;
    union sigval  mysigval;
    
    mysigval.sival_int = 111;
    sigemptyset(&act.sa_mask); //信号处理过程中不阻塞其它信号,
    act.sa_flags = SA_SIGINFO; //表示发送数据外加新的内容
    act.sa_sigaction = msigaction; //回调函数赋值
    sigaction(SIGINT, &act, NULL);  //注册信号
    
    sigqueue(getpid(), SIGINT, mysigval); //发送信号

  

  sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

你可能感兴趣的:(linux第2天)