1、信号
(1)概念
1. 简单 2. 不能携带大量信息 3. 满足某个特设条件才发送。
(2)机制
A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。
信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
(3)产生信号方法
1| 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
2| 系统调用产生,如:kill、raise、abort
3| 软件条件产生,如:定时器alarm
4| 硬件异常产生,如:非法访问内存( 段错误) 、除0( 浮点数例外) 、内存对齐出错( 总线错误)
5| 命令产生,如:kill命令
6| 递达和未决的概念
递达:递送并且到达进程。
未决:产生和递达之间的状态。主要由于阻塞( 屏蔽) 导致该状态。
7| 阻塞信号集(信号屏蔽字)和未决信号集的概念
阻塞信号集(信号屏蔽字):将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后( 解除屏蔽后)
未决信号集:1. 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。
2. 信号产生后由于某些原因( 主要是阻塞) 不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
8| 阻塞信号影响未决信号
(4)信号的处理方式
1| 执行默认动作
< 1> Term:终止进程
< 2> Ign: 忽略信号 ( 默认即时对该种信号忽略操作)
< 3> Core:终止进程,生成Core文件。( 查验进程死亡原因, 用于gdb调试)
< 4> Stop:停止(暂停)进程
< 5> Cont:继续运行进程
2| 忽略( 丢弃)
3| 捕捉( 调用户处理函数)
(5)信号的编号
使用kill -l 的命令来查看,例如( 9) SIGKILL)
(6)信号的四要素
1. 编号 2. 名称 3. 事件 4. 默认处理动作
强调:9) SIGKILL 和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。
(7)Linux常规信号一览表
1) SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2) SIGINT:当用户按下了< Ctrl+C> 组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动
作为终止进程。
3) SIGQUIT:当用户按下< ctrl+\> 组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信
号。默认动作为终止进程。
4) SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5) SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
6) SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7) SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8) SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9) SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10) SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11) SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12) SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13) SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
14) SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。
15) SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
16) SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17) SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。
18) SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19) SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20) SIGTSTP:停止终端交互进程的运行。按下< ctrl+z> 组合键时发出这个信号。默认动作为暂停进程。
21) SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
22) SIGTTOU: 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
23) SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
24) SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。
25) SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
26) SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
27) SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
28) SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
29) SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
30) SIGPWR:关机。默认动作为终止进程。
31) SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
34) SIGRTMIN ~ ( 64) SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。
2、信号的产生
(1)终端按键产生信号
Ctrl + c → 2) SIGINT(终止/中断) "INT" ----Interrupt
Ctrl + z → 20) SIGTSTP(暂停/停止) "T" ----Terminal 终端。
Ctrl + \ → 3) SIGQUIT(退出)
(2)硬件异常产生信号
除0操作 → 8) SIGFPE ( 浮点数例外) "F" -----float 浮点数。
非法访问内存 → 11) SIGSEGV ( 段错误)
总线错误 → 7) SIGBUS
(3)kill函数/命令产生信号
1| 函数原型:int kill( pid_t pid, int sig) ;
2| 返回值:成功:0;失败:-1
3| 参数pid
< 1> pid > 0: 发送信号给指定的进程。
< 2> pid = 0: 发送信号给 与调用kill函数进程属于同一进程组的所有进程。
< 3> pid < 0: 取| pid| 发给对应进程组。
< 4> pid = -1:发送给进程有权限发送的系统中所有进程。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define N 5
int main ( )
{
int i;
pid_t pid, q;
for ( i = 0 ; i < N; i++ )
{
pid = fork ( ) ;
if ( pid < 0 )
{
perror ( "fork error:" ) ;
exit ( 1 ) ;
}
else if ( pid == 0 )
{
break ;
}
if ( i== 2 )
{
q = pid;
}
}
if ( i < 5 )
{
while ( 1 )
{
sleep ( 1 ) ;
printf ( "I am child %d\tgetpid = %u\n" , i+ 1 , getpid ( ) ) ;
}
}
if ( i == 5 )
{
kill ( q, SIGKILL) ;
sleep ( 2 ) ;
printf ( "I am parent\tgetppid = %u\n" , getppid ( ) ) ;
while ( 1 ) ;
}
return 0 ;
}
(4)raise和abort函数
1| raise函数
给当前进程发送指定信号( 自己给自己发) raise( signo) == kill( getpid( ) , signo) ;
int raise( int sig) ; 成功:0,失败非0值
2| abort函数
给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件
void abort( void) ; 该函数无返回
3、软件条件产生信号
(1)alarm函数
1| 函数作用
设置定时器( 闹钟) 。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。
每个进程都有且只有唯一个定时器。
2| 函数原型
unsigned int alarm( unsigned int seconds) ;
3| 返回值
返回0或剩余的秒数,无失败。
常用:取消定时器alarm( 0) ,返回旧闹钟余下秒数。
例:alarm( 5) → 3sec → alarm( 4) → 5sec → alarm( 5) → alarm( 0)
4| 定时,与进程状态无关( 自然定时法) !就绪、运行、挂起( 阻塞、暂停) 、终止、僵尸.. .无论进程处于何种状态,alarm都计时。
5| 实际执行时间 = 系统时间 + 用户时间 + 等待时间
(2)setitimer函数
1| 函数作用
设置定时器( 闹钟) 。 可代替alarm函数。精度微秒us,可以实现周期定时。
2| 函数原型
int setitimer( int which, const struct itimerval *new_value, struct itimerval *old_value) ;
3| 返回值:
成功:0;失败:-1,设置errno
4| 参数:which:指定定时方式
① 自然定时:ITIMER_REAL → 14)SIGLARM 计算自然时间
② 虚拟空间计时( 用户空间) :ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
③ 运行时计时( 用户+内核) :ITIMER_PROF → 27)SIGPROF 计算占用cpu及执行系统调用的时间
5| 提示: it_interval:用来设定两次定时任务之间间隔的时间。
it_value:定时的时长
两个参数都设置为0,即清0操作。
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
unsigned int my_alarm ( unsigned int sec)
{
struct itimerval it, oldit;
int ret;
it. it_value. tv_sec = sec;
it. it_value. tv_usec = 0 ;
it. it_interval. tv_sec = 0 ;
it. it_interval. tv_sec = 0 ;
ret = setitimer ( a) ;
if ( ret == - 1 )
{
perror ( "setitimer error:" ) ;
exit ( 1 ) ;
}
return oldit. it_value. tv_sec;
}
int main ( )
{
int i;
my_alarm ( 1 ) ;
for ( i = 0 ; ; i++ )
{
printf ( "%d\n" , i) ;
}
return 0 ;
}
4、signal捕捉信号
(1)signal函数原型
typedef void ( *sighandler_t) ( int) ; //自定义函数,如果成功捕捉信号,则执行该函数
sighandler_t signal( int signum, sighandler_t handler) ;
(2)实现代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
void myfunc ( int signo)
{
printf ( "hello world" ) ;
}
int main ( )
{
struct itimerval it, oldit;
signal ( SIGALRM, myfunc) ;
it. it_value. tv_sec = 5 ;
it. it_value. tv_usec = 0 ;
it. it_interval. tv_sec = 3 ;
it. it_interval. tv_usec = 0 ;
if ( setitimer ( ITIMER_REAL, & it, oldit) == - 1 )
{
perror ( "setitimer error:" ) ;
return - 1 ;
}
}
5、信号集设定
(1)常见函数(sigset_t类型 例如:sigset_t myset)
int sigemptyset( sigset_t *set) ; 将某个信号集清0 成功:0;失败:-1
int sigfillset( sigset_t *set) ; 将某个信号集置1 成功:0;失败:-1
int sigaddset( sigset_t *set, int signum) ; 将某个信号加入信号集 成功:0;失败:-1
int sigdelset( sigset_t *set, int signum) ; 将某个信号清出信号集 成功:0;失败:-1
int sigismember( const sigset_t *set, int signum) ; 判断某个信号是否在信号集中 返回值:在集合:1;不在:0;出错:-1
sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
(2)sigprocmask函数
1| 函数原型:int sigprocmask( int how, const sigset_t *set, sigset_t *oldset) ;
2| 作用:用来屏蔽信号、解除屏蔽也使用该函数。
3| 参数
set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。(自定义的信号集)
oldset:传出参数,保存旧的信号屏蔽集。(阻塞信号集)
how参数取值: 假设当前的信号屏蔽字为mask
1.SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask| set
2.SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
3.SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于 mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
4| 返回值:成功:0;失败:-1,设置errno
(3)sigpending函数
1| 函数原型:int sigpending( sigset_t *set) ;
2| 作用:读取当前进程的未决信号集
3| 返回值:成功:0;失败:-1,设置errno
#include <stdio.h>
#include <signal.h>
void printped ( sigset_t * ped)
{
int i;
for ( i= 1 ; i< 32 ; i++ )
{
if ( sigismember ( ped, i) == 1 )
{
putchar ( '1' ) ;
}
else
{
putchar ( '0' ) ;
}
}
}
int mian ( )
{
sigset_t myset, oldset, ped;
sigemptyset ( & mtset) ;
sigaddset ( & myset, SIGQUIT) ;
sigprocmask ( SIG_BLOCK, & myset, & oldset) ;
while ( 1 )
{
sigpending ( & ped) ;
printped ( & ped) ;
sleep ( 1 ) ;
}
return 0 ;
}
6、sigaction函数
1| 函数原型:int sigaction( int signum, const struct sigaction *act, struct sigaction *oldact) ;
2| 作用:修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
3| 返回值:成功:0;失败:-1,设置errno
4| 参数
act:传入参数,新的处理方式。
oldact:传出参数,旧的处理方式。 (可以用NULL代替)
5|
struct sigaction结构体
struct sigaction {
void ( *sa_handler) ( int) ;
void ( *sa_sigaction) ( int, siginfo_t *, void *) ;
sigset_t sa_mask;
int sa_flags;
void ( *sa_restorer) ( void) ;
} ;
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。( 弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。( 很少使用)
重点掌握:
① sa_handler:指定信号捕捉后的处理函数名( 即注册函数) 。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合( 信号屏蔽字) 。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags:通常设置为0,表使用默认属性。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void docatch ( int signo)
{
printf ( "%d signal is catched\n" , signo) ;
}
int main ( )
{
int ret;
struct sigaction act;
act. sa_handler = docatch;
sigemptyset ( & act. sa_mask) ;
sigaddset ( & act. sa_mask, SIGQUIT) ;
act. sa_flags = 0 ;
ret = sigaction ( SIGINT, & act, NULL ) ;
if ( ret < 0 )
{
perror ( "sigaction error:" ) ;
exit ( 1 ) ;
}
while ( 1 ) ;
return 0 ;
}
7、pause函数
(1)函数作用
调用该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态( 主动放弃cpu) 直到有信号递达将其唤醒。
(2)函数原型
int pause( void) ;
(3)返回值
返回值:-1 并设置errno为EINTR
< 1> 如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
< 2> 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回。
< 3> 如果信号的处理动作是捕捉,则【调用完信号处理函数之后,pause返回-1】
< 4> errno设置为EINTR,表示“被信号中断”。想想我们还有哪个函数只有出错返回值。
< 5> pause收到的信号不能被屏蔽,如果被屏蔽,那么pause就不能被唤醒。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
void catch_sigalrm ( int signo)
{
;
}
unsigned int mysleep ( unsigned int seconds)
{
int ret;
struct sigaction act oldact;
act. sa_handler = catch_sigalrm;
sigemptyset ( & act. sa_mask) ;
act. sa_flags = 0 ;
ret = sigaction ( SIGALRM, & act, & oldact) ;
if ( ret == - 1 )
{
perror ( "sigaction error:" ) ;
exit ( 1 ) ;
}
alarm ( seconds) ;
ret = pause ( )
if ( ret == - 1 && errno == EINTR)
{
printf ( "pause sucess\n" ) ;
}
ret = alarm ( 0 ) ;
sigaction ( SIGALRM, & oldact, NULL ) ;
return ret;
}
int main ( )
{
while ( 1 )
{
mysleep ( 3 ) ;
printf ( "************\n" ) ;
}
return 0 ;
}
8、时序竞态
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
void catch_sigalrm ( int signo)
{
;
}
unsigned int mysleep ( unsigned int seconds)
{
struct sigaction act, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
act. sa_handler = catch_sigalrm;
sigemptyset ( & act. sa_mask) ;
act. sa_flags = 0 ;
sigaction ( SIGALRM, & act, & oldact) ;
sigemptyset ( & newmask) ;
sigaddset ( & newmask, SIGALRM) ;
sigprocmask ( SIG_BLOCK, & newmask, & oldmask) ;
alarm ( seconds) ;
suspmask = oldmask;
sigdelset ( & suspmask, SIGALRM) ;
sigsuspend ( & suspmask) ;
unslept = alarm ( 0 ) ;
sigaction ( SIGALRM, & oldact, NULL ) ;
sigprocmask ( SIG_SETMASK, & oldmask, NULL ) ;
return unslept;
}
int main ( )
{
while ( 1 )
{
mysleep ( 3 ) ;
printf ( "************\n" ) ;
}
return 0 ;
}
9、全局变量异步I/O
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int n = 0 , flag = 0 ;
void sys_err ( char * str)
{
perror ( str) ;
exit ( 1 ) ;
}
void do_sig_child ( int num)
{
printf ( "I am child %d\t%d\n" , getpid ( ) , n) ;
n + = 2 ;
flag = 1 ;
sleep ( 1 ) ;
}
void do_sig_parent ( int num)
{
printf ( "I am parent %d\t%d\n" , getpid ( ) , n) ;
n + = 2 ;
flag = 1 ;
sleep ( 1 ) ;
}
int main ( void )
{
pid_t pid;
struct sigaction act;
if ( ( pid = fork ( ) ) < 0 )
sys_err ( "fork" ) ;
else if ( pid > 0 ) {
n = 1 ;
sleep ( 1 ) ;
act. sa_handler = do_sig_parent;
sigemptyset ( & act. sa_mask) ;
act. sa_flags = 0 ;
sigaction ( SIGUSR2, & act, NULL ) ;
do_sig_parent ( 0 ) ;
while ( 1 ) {
;
if ( flag == 1 ) {
kill ( pid, SIGUSR1) ;
flag = 0 ;
}
}
} else if ( pid == 0 ) {
n = 2 ;
act. sa_handler = do_sig_child;
sigemptyset ( & act. sa_mask) ;
act. sa_flags = 0 ;
sigaction ( SIGUSR1, & act, NULL ) ;
while ( 1 ) {
;
if ( flag == 1 ) {
kill ( getppid ( ) , SIGUSR2) ;
flag = 0 ;
}
}
}
return 0 ;
}
示例中,通过flag变量标记程序实行进度。flag置1 表示数数完成。flag置0 表示给对方发送信号完成。
10、可重入函数和不可重入函数
一个函数在被调用执行期间( 尚未调用结束) ,由于某种时序又被重复调用,称之为“重入”。
注意事项
1.定义可重入函数,函数内不能含有全局变量及static变量,不能使用malloc、free
2.信号捕捉函数应设计为可重入函数
3.信号处理程序可以调用的可重入函数可参阅man 7 signal
4.没有包含在上述列表中的函数大多是不可重入的,其原因为:
a) 使用静态数据结构
b) 调用了malloc或free
c) 是标准I/O函数
11、SIGCHLD信号
(1)产生条件
1| 子进程终止时
2| 子进程接收到SIGSTOP信号停止时
3| 子进程处在停止态,接受到SIGCONT后唤醒时
(2)借助SIGCHLD信号回收子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err ( char * str)
{
perror ( str) ;
exit ( 1 ) ;
}
void do_sig_child ( int signo)
{
int status; pid_t pid;
while ( ( pid = waitpid ( 0 , & status, WNOHANG) ) > 0 ) {
if ( WIFEXITED ( status) )
printf ( "child %d exit %d\n" , pid, WEXITSTATUS ( status) ) ;
else if ( WIFSIGNALED ( status) )
printf ( "child %d cancel signal %d\n" , pid, WTERMSIG ( status) ) ;
}
}
int main ( void )
{
pid_t pid; int i;
for ( i = 0 ; i < 10 ; i++ ) {
if ( ( pid = fork ( ) ) == 0 )
break ;
else if ( pid < 0 )
sys_err ( "fork" ) ;
}
if ( pid == 0 ) {
int n = 1 ;
while ( n-- ) {
printf ( "child ID %d\n" , getpid ( ) ) ;
sleep ( 1 ) ;
}
return i+ 1 ;
} else if ( pid > 0 ) {
struct sigaction act;
act. sa_handler = do_sig_child;
sigemptyset ( & act. sa_mask) ;
act. sa_flags = 0 ;
sigaction ( SIGCHLD, & act, NULL ) ;
while ( 1 ) {
printf ( "Parent ID %d\n" , getpid ( ) ) ;
sleep ( 1 ) ;
}
}
return 0 ;
}
(3)SIGCHLD信号注意问题
1| 子进程继承了父进程的信号屏蔽字和信号处理动作,但子进程没有继承未决信号集spending
2| 注意注册信号捕捉函数的位置
3| 应该在fork之前,阻塞SIGCHLD信号。注册完捕捉函数后解除阻塞。
12、中断系统调用
(1)慢速系统调用
可能会使进程永远阻塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行( 早期) ;也可以设定系统调用是否重启。如,read、write、pause、wait.. .
(2)其他系统调用
getpid、getppid、fork.. .
(3)慢速系统调用被中断的相关行为,实际上就是pause的行为: 如,read
13、终端(输入设备和输出设备)
(1)相关操作
1| Alt + Ctrl + F1、F2、F3、F4、F5、F6 字符终端
2| pts ( pseudo terminal slave) 指伪终端。
3| Alt + F7 图形终端
4| SSH、Telnet.. . 网络终端
(2)ttyname函数
1| 函数原型:char *ttyname( int fd) ;
2| 作用:由文件描述符查出对应的文件名
3| 返回值:成功:终端名;失败:NULL,设置errno
通过实验看一下各种不同的终端所对应的设备文件名。
int main( void)
{
printf( "fd 0: %s\n" , ttyname( 0)) ;
printf( "fd 1: %s\n" , ttyname( 1)) ;
printf( "fd 2: %s\n" , ttyname( 2)) ;
return 0;
}
14、进程组
(1)注意点
1| 当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID== 第一个进程ID( 组长进程) 。所以,组长进程标识:其进程组ID== 其进程ID
2| 可以使用kill -SIGKILL -进程组ID( 负的) 来将整个进程组内的进程全部杀死。
3| 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
4| 进程组生存期:进程组创建到最后一个进程离开( 终止或转移到另一个进程组) 。
5| 一个进程可以为自己或子进程设置进程组ID
(2)进程组操作函数
1| getpgrp函数(获取当前进程的进程组ID)
< 1> 函数原型:pid_t getpgrp( void) ;
< 2> 返回值:总是返回调用者的进程组ID
2| getpgid函数(获取指定进程的进程组ID)
< 1> 函数原型:pid_t getpgid( pid_t pid) ;
< 2> 返回值:成功:0;失败:-1,设置errno
如果pid = 0,那么该函数作用和getpgrp一样。
3| setpgid函数(改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。)
< 1> 函数原型:int setpgid( pid_t pid, pid_t pgid) ;
< 2> 返回值:成功:0;失败:-1,设置errno
将参1对应的进程,加入参2对应的进程组中。
4| 注意:
1. 如改变子进程为新的组,应fork后,exec前。
2. 权级问题。非root进程只能改变自己创建的子进程,或有权限操作的进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main ( )
{
pid_t pid;
pid = fork ( ) ;
if ( pid == - 1 )
{
perror ( "fork error:" ) ;
exit ( 1 ) ;
}
else if ( pid == 0 )
{
printf ( "child PID == %d\n" , getpid ( ) ) ;
printf ( "child Group ID == %d\n" , getpgid ( 0 ) ) ;
sleep ( 7 ) ;
printf ( "-----Group ID of child is changed to %d\n" , getpgid ( 0 ) ) ;
exit ( 0 ) ;
}
else if ( pid > 0 )
{
sleep ( 1 ) ;
setpgid ( pid, pid) ;
sleep ( 13 ) ;
printf ( "\n" ) ;
printf ( "parent PID == %d\n" , getpid ( ) ) ;
printf ( "parent's parent process PID == %d\n" , getppid ( ) ) ;
printf ( "parent Group ID == %d\n" , getpgid ( 0 ) ) ;
sleep ( 5 ) ;
setpgid ( getpid ( ) , getppid ( ) ) ;
printf ( "\n------Group ID of parent is changed to %d\n" , getpgid ( 0 ) ) ;
while ( 1 ) ;
}
return 0 ;
}
15、会话
(1)创建会话
1| 调用进程不能是进程组组长,该进程变成新会话首进程( session header)
2| 该进程成为一个新进程组的组长进程。
3| 需有root权限( ubuntu不需要)
4| 新会话丢弃原有的控制终端,该会话没有控制终端
5| 该调用进程是组长进程,则出错返回
6| 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
(2)getsid函数
1| 作用:获取进程所属的会话ID
2| 函数原型:pid_t getsid( pid_t pid) ;
3| 返回值: 成功:返回调用进程的会话ID;失败:-1,设置errno
4| 参数说明
< 1> pid为0表示察看当前进程session ID
< 2> 组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
(3)setsid函数
1| 函数作用:创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。
2| 函数原型:pid_t setsid( void) ;
3| 返回值:成功:返回调用进程的会话ID;失败:-1,设置errno
4| 说明
调用了setsid函数的进程,既是新的会长,也是新的组长。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main ( )
{
pid_t pid;
pid = fork ( ) ;
if ( pid == - 1 )
{
perror ( "fork error:" ) ;
exit ( 1 ) ;
}
else if ( pid == 0 )
{
printf ( "child process PID is %d\n" , getpid ( ) ) ;
printf ( "Group ID of child is %d\n" , getpgid ( 0 ) ) ;
printf ( "Session ID of child is %d\n" , getsid ( 0 ) ) ;
sleep ( 10 ) ;
setsid ( ) ;
printf ( "Changed:\n" ) ;
printf ( "child process PID is %d\n" , getpid ( ) ) ;
printf ( "Group ID of child is %d\n" , getpgid ( 0 ) ) ;
printf ( "Session ID of child is %d\n" , getsid ( 0 ) ) ;
sleep ( 20 ) ;
exit ( 0 ) ;
}
return 0 ;
}