linux系统编程基础

系统进程控制管理命令

1、 系统进程概念

1、程序是一个包含可执行代码文件,它放在磁盘的介质上,当程序被操作系统装载到内存并分配给他一定的资源后,此时可以被称为进程。``
	2、程序是静态的概念,进程是动态的概念。

2、并发

并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。

单道程序设计:所有进程一个一个排对执行。若A阻塞,B只能等待,即使CPU处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。
多道程序设计:在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。在多道程序设计模型中,多个进程轮流使用CPU (分时复用CPU资源)。而当下常见CPU为纳秒级,1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

3、进程控制块

每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。

* 进程的状态,有就绪、运行、挂起、停止等状态。
* 进程切换时需要保存和恢复的一些CPU寄存器。
* 描述虚拟地址空间的信息。
* 描述控制终端的信息。
* 当前工作目录(Current Working Directory)。
* umask掩码。
* 文件描述符表,包含很多指向file结构体的指针。
* 和信号相关的信息。
* 用户id和组id。
* 会话(Session)和进程组。
* 进程可以使用的资源上限(Resource Limit)

进程状态:进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

4、常见的环境变量

环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:

①	 字符串(本质) ② 有统一的格式:名=[:] ③ 值用来描述进程环境信息。
②	存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。
使用形式:与命令行参数类似。
加载位置:与命令行参数类似。位于用户区,高于stack的起始位置。
引入环境变量表:须声明环境变量。extern char ** environ;
PATH:可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:$ echo $PATH
SHELL:当前Shell,它的值通常是/bin/bash。
TERN:当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
LANG:语言和locale,决定了字符编码以及时间、货币等信息的显示格式。
HONE:当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。

fork()函数

创建一个子进程。

pid_t fork(void);	失败返回-1;成功返回:
①  父进程返回子进程的ID(非负)	
② ②子进程返回 0 
pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)

注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
创建一个进程

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
        pid_t pid;
        int count =0;
        pid = fork();
        if( pid < 0) 
        {
                printf("error");
        }
        else if(pid==0)
        {
                printf("I am child. my pid=%d parent pid=%d\n",getpid(),getppid());
                exit(1);
        }
        else if(pid>0)
        {
                int temp = 0;
                int ret;
 
                sleep(1);
                printf("I am parent. my pid=%d\n", getpid());
 
                printf(" wait for child to exit ... \r\n");
                ret = wait(&temp);
                while( ret!=-1 )
                {
                        printf("child pid = %ld exit code=%d \n", ret, temp);
                        if(errno == ECHILD)
 break;
                        sleep(1);
                        ret = wait(&temp);
                }
 
                printf(" success for wait for child to exit.\n");
        }
        return 0;
}

一次fork函数调用可以创建一个子进程,那么创建N个子进程,for(i = 0; i < n; i++) { fork() } 即可。

进程共享

父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程ID   2.fork返回值   3.父进程ID    4.进程运行时间    5.闹钟(定时器)   6.未决信号集

getpid函数

获取当前进程ID
    pid_t getpid(void);

getppid函数

获取当前进程的父进程ID
		pid_t getppid(void);
区分一个函数是“系统函数”还是“库函数”依据:
②  是否访问内核数据结构
③  是否访问外部硬件资源,二者有任一 → 系统函数;二者均无 → 库函数

getuid函数

获取当前进程实际用户ID
uid_t getuid(void);
	获取当前进程有效用户ID
uid_t geteuid(void);

getgid函数

获取当前进程使用用户组ID
gid_t getgid(void);
	获取当前进程有效用户组ID
gid_t getegid(void);

孤儿进程

孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。

僵尸进程

僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令指示用来终止进程的,而僵尸进程已经终止。杀掉僵尸进程的方法就是 kill 掉他的父进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/wait.h>

int main(void) {
    pid_t ret = fork();
    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子进程
        printf("child process : i'm going to die.\n");
    } else {
        // 父进程
        while (1) {
            printf("father process : pid = %d, and my son pid = %d\n", getpid(), ret);
            sleep(2);
        }
    }
    return 0;
}

一个进程在终止的时候会关闭所有文件描述符,释放在用户控件分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果进程是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。
一个进程的退出状态可以在 shell 中用 $? 查看,因为 shell 是它的父进程,当它终止时 shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程占用的资源。

当进程终止时,操作系统的隐式回收机制:
1.关闭所有文件描述符;
2.释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止:退出值;异常终止:终止信号)

wait函数

父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出 
② 回收子进程残留资源 
 获取子进程结束状态(退出原因)。
    pid_t wait(int *status); 	成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:
1.关闭所有文件描述符
 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
 1.  WIFEXITED(status) 为非0	→ 进程正常结束
		WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
 2. 	WIFSIGNALED(status) 为非0 → 进程异常终止
		WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. 	WIFSTOPPED(status) 为非0 → 进程处于暂停状态
		WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
		WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/wait.h>

int main(void) {
    pid_t ret = fork();

    if (ret == -1) {
        perror("fork error");
        exit(-1);
    } else if (ret == 0) {
        // 子进程
        printf("child process : i'm going to die.\n");
        exit(101);
    } else {
        // 父进程
        int status = 0;
        pid_t a_ret = wait(&status);
        if (a_ret == -1) {
            perror("wait error");
            exit(-2);
        }
        if (WIFEXITED(status)) {
            printf("child exit with %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

waitpid函数

作用同wait,但可指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);	
成功:返回清理掉的子进程ID;
失败:-1(无子进程)
特殊参数和返回情况:
参数pid: 
> 0 回收指定ID的子进程	
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
			< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行
注意:一次wait或waitpid调用只能清理一个子进程,清 理多个子进程应使用循环
如果所有子进程都正在运行,则调用该函数的进程挂起(因为没有子进程结束,调用该函数的进程阻塞)
如果恰有子进程结束,它的终止状态字等待父进程提取,立即得到该终止状态字并返回,返回值为该字进程的进程号
如果该进程没有子进程,立即返回,返回值为-1

进程命令

1、ps命令

功能:ps命令是用来显示系统瞬间的进程信息,它可以显示出在用户输入ps命令是系统的进程及进程的相关信息。
参数:
-e 显示所有进程,环境变量
-f 用树型格式显示进程
-u按用户名和启动时间的顺序来显示进程
-l 长格式输出
-w 宽输出
-a 显示终端上地所有进程,包括其他用户地进程
-r 只显示正在运行地进程
-x 显示没有控制终端地进程(可组合应用)

2、Top命令:

功能:动态监视系统任务的工具,输出结果是连续的
参数:
-b:批处理,不接受命令行的输入
-c:显示完整的命令行,但不就只是命令名
-d:N显示两次刷新时间的间隔
-I:忽略失效过程
-s:保密(安全)模式,禁用一些效互命令。
-S:累积模式输出每个进程的总CPU时间
-i:<时间> 设置间隔时间,禁止显示空闲进程或僵尸进程
-u:<用户名> 指定用户名
-p:<进程号> 指定进程
-n:<次数> 循环显示的次数,然后退出
-q:不经任何延迟就刷新

3、 kill命令

功能:该命令用于向某个进程(通过PID标识)传达一个信号,它于ps命令和jobs命令使用。
Kill命令格式:kill-signal PID,常用signal参数
1:SIGHUP,启动被终止的进程
2:SIGINT,相当于输入ctrl+c,中断一个程序的进行
9:SIGKILL,强制中断一个进程的进行
15:SIGTERM,以正常的结束进程方式来终止进程
17:SIGSTOP,相当于输入ctrl+z,暂停一个进程的进程

4、 killall命令:

使用进程名称杀死进程,Linux 系统中的killall命令用于杀死指定名字的进程(kill processes by name)。我们可以使用kill命令杀死指定进程PID的进程,如果要找到我们需要杀死的进程,我们还需要在之前使用ps等命令再配合grep来查找进 程,而killall把这两个过程合二为一,是一个很好用的命令。
用法:killall[参数][进程名]
参数:
-Z :只杀死拥有scontext 的进程
-p:杀死进程所属的组
-e :要求匹配进程名称
-I :忽略小写
-g :杀死进程组而不是进程
-i :交互模式,杀死进程前先询问用户
-l :列出所有的已知信号名称
-q :不输出警告信息(如果不杀死进程,则不输出任何信息)
-r:使用正规表达式匹配要杀死的进程名称
-s :发送指定的信号(用指定的进程号代替默认信号“SIGTERM”)
-u:杀死指定用户的进程
-v: 报告信号是否成功发送
-w :等待进程死亡
--help :显示帮助信息
--version: 显示版本显示

5、nice命令

功能:nice命令允许在默认优先级的基础上进行增大或减小的方式来运行命令, 调度优先级是内核分配给进程的代表执行先后可能的整数(-20-20)
整数值越小,优先级越高。
	格式:nice[参数]<command><arguments…>
			Command是系统中任意的可执行文件的名称
			-n  -adjustment 指定程序运行优先级的调整值。
nice命令可以修改进程的优先级,进而调整进程调度。nice值的范围是[-20, 19], -20表示进程的最高优先级,19表示进程的最低优先级。Linux进程的默认nice值为0。使用nice可调整进程的优先级,这样调度器就会依据进程优先级,为其分配CPU资源。
若nice命令未指定优先级的调整值,则以缺省值10来调整程序运行优先级,就是在命令通常运行优先级的基础之上增加10

6、renice命令

功能:renice命令是与nice关联的一个命令,由re两个字母就知道可以重新调整进程执行的优先级,可以指定群组或者用户名调整优先级等级,并修改隶属于该群组或者用户的所有程序优先级。等级范围为[-20,19]。同样仅系统管理员可以拉高优先级。nice在进程拉起时调整,renice在进程执行时调整,改变一个正在运行的程序的nice值。
格式:
	renice[参数]<pid>
	-n :指定程序运行优先级的调整值

7、&命令

功能:在后台运行的命令
		例如:[root@host root]#cp -r /usr/*test&
将/usr目录下的所有子目录及文件复制到/root/test目录下的工作放到后台运行

8、进程的挂起和结束

挂起:ctrl+z
	结束:ctrl+c

9、进程的恢复

恢复到前台继续运行(fg)fg[n]
		恢复到后台继续运行(bg)bg[n]
		查看被挂起的进程:(jobs)

Exec函数族

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。

其实有六种以exec开头的函数,统称exec函数:
int execl(const char *path, const char *arg, ...);// l代表的是list:命令行参数列表
int execlp(const char *file, const char *arg, ...); //p代表的是path:搜索file时的使用的path变量
int execle(const char *path, const char *arg, ..., char *const envp[]); e-environment
int execv(const char *path, char *const argv[]);//v代表的是vector:使用命令行参数数组
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);//e代表的是environment:使用环境变量数组

execlp函数 l-list p-PATH

加载一个进程,借助PATH环境变量	     
		argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数
int execlp(const char *file, const char *arg, ...);		
成功:无返回;失败:-1
    参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
    该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。

execl函数

加载一个进程,通过 路径+程序名 来加载。 
    int execl(const char *path, const char *arg, ...);		成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL);	     使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。

execvp函数

加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式: ①... ② argv[]  (main函数也是变参函数,
形式上等同于 int main(int argc, char *argv0, ...)) 
变参终止条件:① NULL结尾 ② 固参指定
execvp与execlp参数形式不同,原理一致。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
   pid_t pid;
   pid = fork();
   if(pid==-1)
   {
       perror("error");
       exit(1);
   }
  else if(pid>0)
   {
     sleep(1);
      printf("successful!\n");
   }
   else{
    execlp("ls","ls","-l",NULL);                                        
   }
  return 0;
}

gdb调试

使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。		

常见IPC

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

常用的进程间通信方式有:
	① 管道 (使用最简单)
	② 信号 (开销最小)
    ③ 共享映射区 (无血缘关系)
	④ 本地套接字 (最稳定)

管道基础

管道是Linux进程间的一种通信方式,两个进程可以通过一个共享内存区域来传递信息,并且管道中的数据只能是单向流动的,也就是说只能有固定的写进程和读进程。

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区) 
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道可以分为两种类型:匿名管道和命名管道。
其缺点为:
1、读写数据过程中数据自己读不能自己写。
2、数据一旦被读走,便不在管道中存在,不可反复读取(类似水在水管之中的流动)
3、由于管道采用的半双工通信方式。因此数据只能在一个方向流动。
4、只能在有公共祖先的进程之间使用管道
常见的通信方式有,单工通信、半双工通信、全双工通信。

匿名管道

匿名管道只能在父子进程间通讯。
1、	管道内无数据时,读端会发生阻塞直到有数据可读
2、	管道数据满时,写端会发生阻塞,直到读端开始读取数据
3、	如果写端对应的文件描述符被关闭,read函数返回0,但可以将数据读完
4、	如果读端对应的文件描述符被关闭,在执行write函数时会产生SIGPIPE信号,其默认行为会导致当前进程终止

Pipe函数

创建管道
int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。
pipe()函数用于在内核中创建一个管道,该管道一端用于读取管道中的数据,另一端用于将数据写入管道。在创建一个管道后,会获得一对文件描述符,用于读取和写入,然后将参数数组fd中的两个值传递给获取到的两个文件描述符,fd[0]指向管道的读端,fd[1]指向写端。
pipe()函数调用成功,返回值为0;否则返回-1,并且设置了适当的错误返回信息。此函数只是创建了管道,要想从管道中读取数据或者向管道中写入数据,需要使用read()和write()函数来完成。当管道通信结束后,需要使用close()函数关闭管道的读写端。

实现父子进程间通讯
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>

int main() {
	int fd[2];
	if (pipe(fd) == -1) {
		fprintf(stdout, "Can not open pipe.\n");
		exit(1);
	}
	pid_t pid = fork();
	if (pid < 0) {
		fprintf(stdout, " failure\n");
	} else if (pid == 0) {  //子进程
		close(fd[0]);  //子进程关闭读端
		char str[] = "message";  //将"message"传递给父进程
		write(fd[1], str, strlen(str));
	} else {   //父进程
		close(fd[1]);  //父进程关闭写端
		char str[100];
		read(fd[0], str, 100);
		fprintf(stdout, "Parent: read data from pipeline (%s)", str);
	}
    return 0;
}

命名管道

命名管道本质上是一个管道文件,它基于文件系统来实现进程间的通信,其读写端进程可以不是父子进程的关系,只需要进程有权限访问该管道文件即可。需要注意的是,命名管道中的数据实际上是存储在内存中,管道文件在文件系统中相当于是一个标记。
	其命名规则
在命令行界面通过命令mkfifo filename创建命名管道文件,可以指定其文件名
在程序内部调用mkfifo函数(定义于头文件sys/stat.h中),参数filename为管道文件路径,mode为管道文件读写权限:
例如:int mkfifo(const char *filename, mode_t mode);
	命名管道的读写机制和匿名管道相似。
只不过在使用前我们需要调用open函数来打开管道文件,通过其返回的文件描述符来读写管道文件。

Mkfifo函数命名管道

使用mkfifo()函数创建的命名管道文件与前面介绍的管道通信相似,只是它们创建方式不同。访问命名管道文件与访问文件系统中的其他文件一样,都是需要首先打开文件,然后对文件进行读写数据。如果在命名管道文件中读取数据时,并没有其他进程向命名管道文件中写入数据,则会出现进程阻塞状态;如果在写入数据的同时,没有进程从命名管道中读取数据,也会出现进程阻塞状态。
用mkfifo()函数创建一个命名管道,命名管道文件路径
调用open()函数打开该命名管道文件,以读写的方式打开。
调用write()函数向文件写入信息"hello world", 同时调用read()函数读取该文件,输出到终端。
调用close()函数关闭打开的命名管道文件。

FIFO函数

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。
	FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。
创建方式:
1. 命令:mkfifo 管道名
	2. 库函数:int mkfifo(const char *pathname,  mode_t mode);  成功:0; 失败:-1
	一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define FIFO "/root/process/hello"

int main()
{
    int fd;
    int pid;
    char r_msg[BUFSIZ];
    if((pid = mkfifo(FIFO,0777))==-1) /*创建命名管道*/
    {
	perror("failed!");
	return 1;
    }
    else
    printf("success!\n");
    fd = open(FIFO, O_RDWR);  /*打开命名管道*/
    if(fd == -1)
    {
	perror("cannot open the FIFO");
	return 1;
    }
    if(write(fd,"hello world", 12) == -1)  /*写入消息*/
    {
	perror("write data error!");
	return 1;
    }
    else
    printf("write data success!\n");
    if(read(fd, r_msg, BUFSIZ) == -1)  /*读取消息*/
    {
	perror("read error!");
	return 1;
    }
    else
    printf("the receive data is:  %s\n",r_msg);
    close(fd);   /*关闭文件*/
    return 0;
}

管道的读写

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
总结:
① 读管道:	1. 管道中有数据,read返回实际读到的字节数。
				2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
				 	(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
    ② 写管道:	1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
				2. 管道读端没有全部关闭: 
(1) 管道已满,write阻塞。
					(2) 管道未满,write将数据写入,并返回实际写入的字节数。

管道编程技术

dup函数和dup2函数

dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。它们
常用来重定向进程的stdin、stdout和stderr。

函数的原形如下:
#include <unistd.h>   
int dup( int oldfd );   
int dup2( int oldfd, int targetfd )
利用函数dup,我们可以复制一个描述符。传给该函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符是传给它的描述符的拷贝。这意味着,这两个描述符共享同一个数据结构。
 	dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
int pfds[2];
if ( pipe(pfds) == 0 ) ...{
if ( fork() == 0 ) ...{
close(1);
dup2( pfds[1], 1 );
close( pfds[0] );
execlp( "ls", "ls", "-1", NULL );
} else ...{
close(0); 
dup2( pfds[0], 0 );
 close( pfds[1] );    
 execlp( "wc", "wc", "-l", NULL );
}
}
return 0;
}

open函数

open 函数用于打开和创建文件。

#include <fcntl.h>
int open(const char *pathname, int oflag, ... );
返回值:成功则返回文件描述符,否则返回 -1

对于 open 函数来说,第三个参数(…)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。

O_RDONLY	只读模式
O_WRONLY	只写模式
O_RDWR	 读写模式

打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:

O_APPEND	 每次写操作都写入文件的末尾
O_CREAT	如果指定文件不存在,则创建这个文件
O_EXCL	 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC	如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY	如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK	如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

以下三个常量同样是选用的,它们用于同步输入输出

O_DSYNC	等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC		read 等待所有写入同一区域的写操作完成后再进行
O_SYNC	等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

补充read函数和write函数用法

头文件
#include<unistd.h>
读函数read 
ssize_t read(int fd,void *buf,size_t nbyte) 
read函数是负责从 fd中读取内容到buf中.成功时,read返回实际所读的字节数,如果返回的值是0,表示已经读到文件的结束了.
小于0表示出现了错误.如果错误为 EINTR说明读是由中断引起的, 如果是ECONNREST表示网络连接出了问题。
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
        int file1;
        file1=open("./1.txt",O_RDWR);//以读写形式打开创建的文件
        lseek(file1,0,0);//将光标设置在文件开头
        char s3[128];
        int num=read(file1,s3,128);//第一个参数所读文件的文件标识符,第二个参数通常是一个字符数组
        //第三个参数所要读取的字节数,返回值是实际所读取的的字节数
        close(file1);//关闭文件
        printf("%s",s3);//文件已经被读入到了s3中,输出所读的文件
        return 0; 
}

写函数write

ssize_t write(int fd,const void *buf,size_t nbytes)
write函数将buf中的nbytes字节内容写 入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量. 在网络程序中,当我们向套接字文件描述符写时有俩种可能.
1)write 的返回值大于0,表示写了部分或者是全部的数据. 
2)返回的值小于0,此时出现了错误.我们要根据错误类型来处理.  如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接).
3)nbytes 可以大于实际能写入的大小,比如,缓冲区只能写入100,但却写入1000,前100个字节能写入成功。可利用这个特性加上移动指针反复写入
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
        int file1;
        char s1[]="adcdefghijklmn";//创建字符数组
        file1=open("./1.txt",O_CREAT|O_EXCL,0644);//创建文件
        if(file1==-1)
        {
                perror("创建文件file1失败");

        }
        file1=open("./1.txt",O_RDWR);//打开文件
        if(file1!=-1)
        {
                printf("文件file1打开成功\n");
                write(file1,s1,strlen(s1));//写入文件
                printf("文件file1写入成功\n");
        }
        close(file1);
        return 0; 
}

存储映射I/O

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成	I/O操作。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

mmap函数

1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
函数:void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length:代表将文件中多大的部分映射到内存。
参数prot:映射区域的保护方式。可以为以下几种方式的组合:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取
参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
返回值:
若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(1),错误原因存于errno 中。
错误代码:
EBADF 参数fd 不是有效的文件描述词
EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
EINVAL 参数start、length 或offset有一个不合法。
EAGAIN 文件被锁住,或是有太多内存被锁住。
ENOMEM 内存不足。
总结:使用mmap时务必注意以下事项:
1.	创建映射区的过程中,隐含着一次对映射文件的读操作。
2.	当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
3.	映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
4.	特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!	mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
5.	munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
6.	如果文件偏移量必须为4K的整数倍
7.	mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

mmap父子进程通信

父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:
MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
MAP_SHARED: (共享映射) 父子进程共享映射区;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
 
int var = 100;  //全局变量
 
int main(void)
{
    int *p;
    pid_t pid;
 
    int fd;
    fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if(fd < 0){
        perror("open error");
        exit(1);
    }
    unlink("temp");        //删除临时文件目录项,使之具备被释放条件,该文件没有存在的必要,仅用于完成映射区,来用于父子进程间通信,因此unlink。
    ftruncate(fd, 4);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED) {                
        perror("mmap error");
        exit(1);
    }
    close(fd);                   //映射区建立完毕,即可关闭文件
 
    pid = fork();                               //创建子进程
    if(pid == 0){
        *p = 2000;
        var = 1000;
        printf("child, *p = %d, var = %d\n", *p, var);
    } else {
        sleep(1);
        printf("parent, *p = %d, var = %d\n", *p, var);
        wait(NULL);
 
        int ret = munmap(p, 4);                 //释放映射区
        if (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }
 
    return 0;
}

mmap无血缘关系进程间通信

实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了。

写数据
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
 
struct STU {
    int id;
    char name[20];
    char sex;
};
 
void sys_err(char *str)
{
    perror(str);
    exit(1);
}
 
int main(int argc, char *argv[])
{
    int fd;
    struct STU student = {10, "xiaoming", 'm'};
    struct STU *mm;
 
    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }
 
    fd = open(argv[1], O_RDWR | O_CREAT, 0664);
    ftruncate(fd, sizeof(student));
 
    mm = mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap");
 
    close(fd);
 
    while (1) {
        memcpy(mm, &student, sizeof(student));
        student.id++;
        sleep(1);
    }
 
    munmap(mm, sizeof(student));
 
return 0;
}
读数据
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
 
struct STU {
    int id;
    char name[20];
    char sex;
};
 
void sys_err(char *str)
{
    perror(str);
    exit(-1);
}
 
int main(int argc, char *argv[])
{
    int fd;
    struct STU student;
    struct STU *mm;
 
    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }
 
    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        sys_err("open error");
 
    mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap error");
    close(fd);
    while (1) {
        printf("id=%d\tname=%s\t%c\n", mm->id, mm->name, mm->sex);
        sleep(2);
    }
    munmap(mm, sizeof(student));
 
return 0;
}

munmap函数

同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
int munmap(void *addr, size_t length);	成功:0; 失败:-1

匿名映射

使用特殊文件提供匿名内存映射:
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
另外使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。
使用MAP_ANONYMOUS (或MAP_ANON), 如:
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
"4"随意举例,该位置表大小,可依实际需要填写。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
 
int var = 100;  //全局变量
 
int main(void)
{
    int *p;
    pid_t pid;
 
   
    p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED) {                
        perror("mmap error");
        exit(1);
    }
    pid = fork();                               //创建子进程
    if(pid == 0){
        *p = 2000;
        var = 1000;
        printf("child, *p = %d, var = %d\n", *p, var);
    } else {
        sleep(1);
        printf("parent, *p = %d, var = %d\n", *p, var);
        wait(NULL);
 
        int ret = munmap(p, 4);                 //释放映射区
        if (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }
 
    return 0;
}

需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。
② fd = open("/dev/zero", O_RDWR);
② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
 
int var = 100;  //全局变量
 
int main(void)
{
    int *p;
pid_t pid;
int fd = open(/dev/zero”,O_RDWR);

p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED,fd, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED) {                
        perror("mmap error");
        exit(1);
    }
    pid = fork();                               //创建子进程
    if(pid == 0){
        *p = 2000;
        var = 1000;
        printf("child, *p = %d, var = %d\n", *p, var);
    } else {
        sleep(1);
        printf("parent, *p = %d, var = %d\n", *p, var);
        wait(NULL);
 
        int ret = munmap(p, 4);                 //释放映射区
        if (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }
 
    return 0;
}

信号的概念

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

信号的机制

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送的,内核处理。

与信号相关的事件和状态

```c
产生信号: 
1. 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
2. 系统调用产生,如:kill、raise、abort
3. 软件条件产生,如:定时器alarm
4. 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
5. 命令产生,如:kill命令
	递达:递送并且到达进程。
	未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。

信号的处理方式

1. 执行默认动作 
2. 忽略(丢弃) 
3. 捕捉(调用户处理函数)
	Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)
未决信号集: 
1. 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。 
2. 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。    

信号的编号

可以使用kill –l命令查看当前系统可使用的信号有哪些。

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 	5) SIGTRAP
 	6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。

Linux常规信号一览表

1、SIGHUP
本信号在用户终端连接(正常或非正常)结束退出时候发出,通常是在终端的控制进程结束时,通知同一session 内的各个作业,这时它们与控
制终端不再联系。
 登录Linux时,系统会分配给登录用户一个终端(session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个session。当用户退出Linux登录时,前台进程组和后台进程组有对终端输出的进程将会收到SIGHUP信号。这个信号默认操作为终止进程,因此前台进程组和后台进程组有终端输出的进程就会终止。不过可以捕获这个信号,比如:wget能捕获SIGHUP信号,并忽略它,这样就算退出来Linux登录,wget也能继续下载。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2、SIGINT
程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3、SIGQUIT
 和SIGINT类似,但由于QUIT字符(通常是Ctrl-\)来控制。进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误
 信号。
4、SIGILL
执行了非法指令。通常是因为可执行文件本身出现错误,或者试图执行数据段,堆栈溢出时也可能产生这个信号。
5、SIGTRAP
有断点指令或其他指trap令产生。由debugger使用。
6、SIGABRT
调用abort函数生成的辛哈。
7、SIGBUS
非法地址,包括地址内存对齐(alignment)出错。比如访问一个四个字节长的整数,但其地址不是4的倍数,它与SIGSGV的区别在于后者是
由于对合法存储地址的非法访问触发的(如访问不属于自己的存储空间或只读存储空间)。
8、SIGFPE
在发生致命的算术运算错误时发出,不仅包括浮点运算符错误,还包括溢出及除数为0等其他所有的算术的错误。
9、SIGKILL
用来立即结束程序的运行,本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10、SIGUSR1
留给用户使用。
11、SIGSEGV
 试图访问未非配给自己的内存,或试图往没有写权限对内存地址写数据。
12、SIGUSR2
留给用户使用。
13、SIGPIPE
管道破裂,这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没有打开或者意外终止就往管道写,写进程会
收到SIGPIPE信号。此外用socket通信的两个进程,谢金成在写socket的时候,读进程已经终止。
14、SIGALRM
时钟定时信号,计算的时实际时间或者是时钟时间,alarm函数使用该信号。
15、SIGTERM
程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理,通常用来要求程序正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17、SIGCHLD
子进程结束时,父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占用表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHLD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进行来接管)。
18、SIGCONT
 让一个停止(stopped)的进程继续执行,本信号不能被阻塞。可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作,例如,重新显示提示符。
19、SIGSTOP
停止(stopped)进程的执行,注意它和terminate以及interrupt的区别:该进程还未结束,本信号不能被阻塞、处理或忽略。
20、SIGTSTP
停止进程的运行,但是该信号可以被处理或者忽略,用户键入SUSP字符时(通常Ctrl-Z)发出这个信号。
21、SIGTTIN
当后台作业要求用户终端读数据时,改作业中的所有进程会收到SIGTTIN信号,缺省时这些进程会停止执行。
22、SIGTTOU
 类似于SIGTTIN,但是写终端(或修改终端模式)时收到。
23、SIGURG
有“紧急”数据或out-of-band数据到达socket时产生。
24、SIGXCPU
超过CPU时间资源限制,这个限制可以由getrlimit/setrlimit来读取/改变。
25、SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
26、SIGVTALRM
虚拟时钟信号,类似于SIGALRM,但是计算的是该进程占用的CPU时间
27、SIGPROF
 类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用时间。
28、SIGWINCH
窗口大小改变时发出。
29、SIGIO
文件描述符准备就绪,可以开始进行输入/输出操作。
30、SIGPWR
Power failure
31、SIGSYS
非法的系统调用
在以上列出的信号中,程序不可以捕获、阻塞或忽略的信号有:SIGKILL、SIGSTOP
不能恢复至默认动作的信号有:SIGABRT、SIGBUS、SIGILL、SIGFPE、SIGIOT、SIGQUIT、SIGSEGV、SIGTRAP、SIGXCPU、SIGFSZ
 默认会导致进程退出的信号有:SIGALRM、SIGHUP、SIGINT、SIGKILL、SIGPIPE、SIGPOLL、SIGPROF、SIGSYS、SIGTERM、SIGUSR1、SIGUSR2、SIGVTALRM
 默认会导致进程停止的信号有:SIGSTOP、SIGSTP、SIGTTIN、SIGTTOU
默认进程忽略的信号有:SIGCHLD、SIGPWR、SIGURG、SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中忽略;SIGCONT在进程挂起时是继续、否则是忽略,不能被阻塞。

信号4要素

与变量三要素类似的,每个信号也有其必备4要素,分别是:

  1. 编号 2. 名称 3. 事件 4. 默认处理动作
    可通过man 7 signal查看帮助文档获取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h
    Signal Value Action Comment
    ────────────────────────────────────────────
    SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process
    SIGINT 2 Term Interrupt from keyboard
    SIGQUIT 3 Core Quit from keyboard
    SIGKILL 9 Term Kill signal
    SIGSEGV 11 Core Invalid memory reference
    SIGPIPE 13 Term Broken pipe: write to pipe with no readers
    SIGCHLD 20,17,18 Ign Child stopped or terminated
    SIGCONT 19,18,25 Cont Continue if stopped
    SIGSTOP 17,19,23 Stop Stop process
    The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
默认动作:
	Term:终止进程
	Ign: 忽略信号 (默认即时对该种信号忽略操作)
	Core:终止进程,生成Core文件。(查验进程死亡原因, 用于gdb调试)
	Stop:停止(暂停)进程
	Cont:继续运行进程
  这里特别强调了9) SIGKILL 和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。
另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达),不应乱发信号!!

信号的产生

终端按键产生信号
    Ctrl + c  → 2) SIGINT(终止/中断)	 "INT" ----Interrupt
    Ctrl + z  → 20) SIGTSTP(暂停/停止)  "T" ----Terminal 终端。
    Ctrl + \  → 3) SIGQUIT(退出)	
硬件异常产生信号
    除0操作   → 8) SIGFPE (浮点数例外)	"F" -----float 浮点数。
    非法访问内存  → 11) SIGSEGV (段错误)
总线错误  → 7) SIGBUS	

捕获信号

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sigact(int  num)
{
    printf("\n %d号 信号 被我吃了. \n", num);
}

int main()
{
    printf("catch start ... \n");
    signal(SIGINT, sigact); // 捕捉 SIGINT 信号,提供自定义动作
    while(1)
    {
        sleep(1);
        printf("你杀不掉我 hhh \n");
    }
    return 0;
}

Kill函数操作

kill命令产生信号:kill -SIGKILL pid
kill函数:给指定进程发送指定信号(不一定杀死)
    int kill(pid_t pid, int sig);	 成功:0;失败:-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno
	sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
    pid > 0:  发送信号给指定的进程。
	pid = 0:  发送信号给 与调用kill函数进程属于同一进程组的所有进程。
	pid < 0:|pid|发给对应进程组。
	pid = -1:发送给进程有权限发送的系统中所有进程。
   进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
	权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。 kill -9 (root用户的pid)  是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

循环创建进程,杀死某一特定进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>

/*
 * 循环创建 5 个子进程                                                             
 */

#define N  5
int main()
{
    int i;
    pid_t pid,q;

    for(i=0;i<N;i++){
        pid = fork();
        if(pid == 0)
            break;
        if(i==2)
            q=pid;
    }
    if(i<5){
        while(1){
            printf("I am child %d,getpid= %d\n",i,getpid());
            sleep(1);
        }   
    }else{
        sleep(1);
        kill(q,SIGKILL);
        while(1);
    }   
    return 0;
}

raise和abort函数

raise 函数:给当前进程发送指定信号(自己给自己发)	raise(signo) == kill(getpid(), signo);
	int raise(int sig); 成功:0,失败非0值
abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件
	void abort(void); 该函数无返回

alarm函数

每个进程都有且只有唯一个定时器。
unsigned int alarm(unsigned int seconds)
函数作用:在seconds秒之后向调用alarm()的进程发送一个SIGALRM信号。
1.如果指定seconds是0,表示取消正在等待的alarm,如果在等待时间结束之前有其它事件到来,alarm也将被取消。
2.对于一个进程而言,只有最近的依次alarm()调用是有效的。alarm()的返回值是上次alarm()调用剩余的时间。
alarm()函数经常与signal(int signum, sighandler_t handler)函数一起使用,通过signal()函数可以指定受到该信号后的动作。
signum是要处理的信号类型
handler是一个函数指针,指向接到信号后的相应动作
linux所有的事件类型可以通过kill -l命令查看所有的信号类型的宏定义
通过man signal和man alarm可分别查看函数的帮助文档
使用time命令查看程序执行的时间。
	实际执行时间 = 系统时间 + 用户时间 + 等待时间

一秒钟计算的值

#include <stdio.h>
#include <unistd.h>

int main()
{
    int count = 0;

    alarm(1);
    while(1)
    {
        count++;
        printf("count is  %d .\n", count);
    }
    return 0;
}

setitimer函数

头文件#include<sys/time.h>
设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
    int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);	成功:0;失败:-1,设置errno
	参数:which:指定定时方式
	① 自然定时:ITIMER_REAL → 14)SIGLARM				 		计算自然时间
	② 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  	 只计算进程占用cpu的时间
	③ 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF		 计算占用cpu及执行系统调用的时间
struct itimerval *new_value定义如下:
struct itimerval {
               struct timeval it_interval; /* next value */
               struct timeval it_value; /* current value */
           };

Struct timeval定义如下
struct timeval {
               long tv_sec; /* seconds */
               long tv_usec; /* microseconds */
          };

Setitime函数简单用法

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<stdlib.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_usec = 0;
ret = setitimer(ITIMER_REAL, &it, &oldit);
if(ret == -1)
{
perror("setitimer\n");
exit(1);
}
return oldit.it_value.tv_sec;
}
int main()
{
my_alarm(1);//alarm(1);
int i;
for(i=0;;i++)
{
i++;
printf("%d\n",i);
}
return 0;
}

使用setitime函数制作间隔计时器

#include <stdio.h>       
#include <unistd.h>      
#include <signal.h>       
#include <string.h>       
#include <sys/time.h>   

static int count = 0;

void printMes(int signo)
{
    printf("Get a SIGALRM, %d counts!\n", ++count);
}

int main()
{
    int res = 0;
    struct itimerval tick;
    
    signal(SIGALRM, printMes);
    memset(&tick, 0, sizeof(tick));
    tick.it_value.tv_sec = 1;
    tick.it_value.tv_usec = 0;
    tick.it_interval.tv_sec = 1;
    tick.it_interval.tv_usec = 0;

    if(setitimer(ITIMER_REAL, &tick, NULL) < 0)
            printf("Set timer failed!\n");
    while(1)
    {
        pause();
    }
    return 0;
}

sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该。函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢处理。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);	成功:0;失败:-1,设置errno
参数:
		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返回前,至少将其中一个信号递达。
如果set是空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
#include <stdio.h>
#include <signal.h> 
void checkset(int i); 
 
void main()    
{
     sigset_t blockset;    
     sigemptyset(&blockset);    
     sigaddset(&blockset,SIGINT);    
     sigaddset(&blockset,SIGTSTP);    
 
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     checkset(0);    
 
     sigprocmask(SIG_SETMASK,&blockset,NULL);    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
    checkset(1);    
 
     sigaddset(&blockset,SIGTERM);    
     sigprocmask(SIG_BLOCK,&blockset,NULL);    
     checkset(2);    
 
     //sigdelset(&blockset,SIGTERM);     
     sigprocmask(SIG_UNBLOCK,&blockset,NULL);    
     if(sigismember(&blockset,SIGTERM))    
       printf("sig term in main\n");    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     if(sigismember(&blockset,SIGTSTP))    
       printf("sig tstp in main\n");    
     checkset(3);
     if(sigismember(&blockset,SIGTERM))    
       printf("sig term in main\n");    
     if(sigismember(&blockset,SIGINT))    
       printf("sig int in main\n");    
     if(sigismember(&blockset,SIGTSTP))    
       printf("sig tstp in main\n");
}
 
void checkset(int i)
{
 
     sigset_t set;
     printf("check set start:%d\n", i);
 
     if(sigprocmask(SIG_BLOCK,NULL,&set)<0)
    {
       printf("check set sig procmask error!!\n");
       exit(0);
     }
 
     if(sigismember(&set,SIGINT))
       printf("sig int\n");
     if(sigismember(&set,SIGTSTP))
       printf("sig tstp\n");
     if(sigismember(&set,SIGTERM))
       printf("sig term\n");
 
     printf("check set end\n\n");
}

sigpending函数

读取当前进程的未决信号集
int sigpending(sigset_t *set);	set传出参数。   返回值:成功:0;失败:-1,设置errno

打印未决信号集

#include 
#include 
#include 
void printPending(sigset_t *set)
{
int i = 0;
for (i = 0; i < 32; i++) {
if (sigismember(set, i) == 1)
printf("1");
else
 printf("0");
}
 printf("\n");
}
int main()
{
sigset_t set, oldset, pendset;
sigemptyset(&set);
sigaddset(&set,SIGQUIT);   // ctrl + \ 将产生SIGQUIT信号
sigprocmask(SIG_BLOCK, &set, &oldset);
 while (1) {
sigpending(&pendset);
printPending(&pendset);     // 写一个函数打印未决信号集
sleep(1);
}
}

signal函数

注册一个信号捕捉函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
	该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。
void (*signal(int signum, void (*sighandler_t)(int))) (int);
注意多在复杂结构中使用typedef
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
typedef void (*sighandler_t) (int );
void catchsigint(int signo)
{
Printf(“…………..catch\n”);
}
Int main()
{
sighandler_t handler;
handler = signal(SIGINT,catchsigint);
if(handler == SIG_ERR)
{
perror(“signal error”);
exit(1);
}
while(1)
return 0;
}

sigaction函数

修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  成功:0;失败:-1,设置errno
参数:
act:传入参数,新的处理方式。
		oldact:传出参数,旧的处理方式。													【signal.c】
struct sigaction结构体
    struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_ )(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 <stdlib.h>
#include <signal.h>
 
int main()
{
    struct sigaction newact,oldact;
 
    /* 设置信号忽略 */
    newact.sa_handler = SIG_IGN; //这个地方也可以是函数
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    int count = 0;
    pid_t pid = 0;
 
    sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
 
    pid = fork();
    if(pid == 0)
    {
        while(1)
        {
            printf("I'm child gaga.......\n");
            sleep(1);
        }
        return 0;
    }
 
    while(1)
    {
        if(count++ > 3)
        {
            sigaction(SIGINT,&oldact,NULL);  //备份回来
            printf("pid = %d\n",pid);
            kill(pid,SIGKILL); //父进程发信号,来杀死子进程
        }
 
        printf("I am father .......... hahaha\n");
        sleep(1);
    }
 
    return 0;
}

信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以在应用程序中自定义set来改变mask。已达到屏蔽指定信号的目的。

信号集设定
	sigset_t  set;		// typedef unsigned long sigset_t; 
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类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

对比认知select 函数。

Select函数

头文件及函数原型
/* According to POSIX.1-2001 */
       #include <sys/select.h>  //头文件
 
       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
 
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);//函数原型
该函数允许进程指示等待多个事件的任何一个发生,并且只在有一个或多个事件发生或者经历一段指定的时间后才唤醒它
它只有在如下四种情况下返回
1. 集合中的任何描述符准备好读
2. 集合中的任何描述符准备好谢
3. 集合中的任何描述符有异常等待处理
4. 等待事件到达
等待事件这个参数有三种可能
1.永远等下去,仅在有一个描述符准备好才返回,为此,我们把该参数设置为空指针
2.等待一段固定时间,在有一个描述符准好好I/O才返回,但是不能超时;
3. 不等待,检查描述符后立即返回,这时定时器的时间必须设置为0
返回值:
(1)正常情况下返回满足要求的文件描述符个数;
(2)经过了timeout等待后仍无文件满足要求,返回0;
(3)如果select被某个信号中断,将返回-1并设置errno为EINTR;
(4)若出错,返回-1并设置相应的errno;

select的使用方法:

1)将要监控的文件添加到文件描述符集;
(2)调用select开始监控;
(3)判断文件是否发生变化;
系统提供四个宏对描述符集进行操作:
void FD_SET(int fd, fd_set *fdset); //将文件描述符fd添加到文件描述符集fdset中;
void FD_CLR(int fd, fd_set *fdset); //从文件描述符集fdset中清除文件描述符fd;
void FD_ISSET(int fd, fd_set *fdset); //在调用select后使用FD_ISSET来检测文件描述符集中的文件fd发生了变化
void FD_ZERO(fd_set *fdset);//清空文件描述符集

注意:
每次调用完select()函数后需要将文件描述符集合清空并重新设置,也就是设置的文件描述符集合是一次性使用的。原因是调用完select()后文件描述符集合可能发生改变。

#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd_key, ret;
    char value;
    fd_set readfd;
    struct timeval timeout;

    fd_key = open("/dev/tty", O_RDONLY);
    timeout.tv_sec=1;
    timeout.tv_usec=0;

    while(1){
        FD_ZERO(&readfd);                       /* 清空文件描述符集合 */
        FD_SET(fd_key, &readfd);                /* 添加文件描述符到集合 */

        ret = select(fd_key + 1, &readfd, NULL, NULL, &timeout);
        if(FD_ISSET(fd_key, &readfd)){          /* 测试fd_key是否在描述符集合中 */
            read(fd_key, &value, 1);  
            if('\n' == value){
                continue;
            }
            printf("ret = %d, fd_key = %d, value = %c\n", ret, fd_key, value);
        }
    }
}

信号捕捉特性

1.	进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
2.	XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>

void docatch(int signo)
{
printf(%d signal is catched\n”,signo);
sleep(1);
printf(“……………….finish\n”);
}

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;
}
发布了20 篇原创文章 · 获赞 1 · 访问量 2502

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/104296545