linux操作系统学习第五篇博客

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>

/*struct itimerval{
	 struct timeval{
	 	it_value.tv_sec;      表示秒
		it_value.tv_usec;     表示毫秒 
	 }it_interval;
	 
	 struct timeval{
	 	it_value.tv_sec;      表示秒
		it_value.tv_usec;     表示毫秒 
	 }it_value;
}it,oldit;*/

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); //alarm(sec)
	
	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);  //注册SIGALRM信号的捕捉处理函数
	
	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++)       //1到32的信号,用kill -l 查看 
	{
		if(sigismember(ped,i)==1)
		{
			putchar('1');	
		} 
		else
		{
			putchar('0');
		}
	}
}
int mian()
{
	sigset_t myset,oldset,ped;
	//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()         //主动挂起,等待alarm发的信号SIGALRM
	if(ret == -1 && errno == EINTR)
	{
		printf("pause sucess\n");
	}
	
	ret = alarm(0);
	sigaction(SIGALRM,&oldact,NULL);  //回复SIGALRM信号旧有的处理方式 
	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;
	
	//1.为SIGALRM设置捕捉函数,一个空函数 
	act.sa_handler = catch_sigalrm;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGALRM,&act,&oldact);
	
	//2.设置阻塞信号集,阻塞SIGALRM信号
	sigemptyset(&newmask);
	sigaddset(&newmask,SIGALRM);
	sigprocmask(SIG_BLOCK,&newmask,&oldmask);   //信号屏蔽字mask
	
	//3.定时n秒,到时后可以产生SIGALRM信号
	alarm(seconds);
	
	//4.构造一个调用sigsuspend临时有效的阻塞信号集
	//在临时阻塞信号集里面解除SIGALRM信号的阻塞
	suspmask = oldmask;                       //SIGALRM没有被屏蔽 
	sigdelset(&suspmask,SIGALRM);
	
	//5. sigsuspend调用期间,采用临时阻塞信号集suspmask替换原有阻塞信号集
	//这个信号集中不包括SIGALRM信号,同时挂起等待
	//当sigsuspend被信号唤醒返回时,恢复原有的阻塞信号集
	sigsuspend(&suspmask); 
	
	unslept = alarm(0);
	
	//6.恢复SIGALRM原有的处理动作,呼应前面的注释1
	sigaction(SIGALRM,&oldact,NULL);
	
	//7.解除对SIGALRM的阻塞,呼应前面注释2
	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);             //注册自己的信号捕捉函数  父使用SIGUSR2信号
        do_sig_parent(0);						  
        while (1) {
            /* wait for signal */;
           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) {
            /* waiting for a signal */;
            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信号回收子进程

//子进程结束运行,其父进程会收到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		
通过实验看一下各种不同的终端所对应的设备文件名。
#include <unistd.h>
#include <stdio.h>
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));  //返回组ID
		//printf("child Group ID == %d\n",getpgrp());   //返回组ID
		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);        //让子进程自立门户,成为进程组组长,以他的pid为进程ID	
	
		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());    //改变父进程的组id为父进程的父进程 
		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();     //子进程非组长进程,故其成为新会话首进程,且成为组长进程,该进程组id即为会话进程
		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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值