【提高】【Linux】【二】C++学习日记

环境变量

在操作系统中用来指定操作系统运行环境的一些参数
特征:字符串(本质)
           有统一的格式:名=值[:值]
           值用来描述进程环境信息
存储形式:与命令行参数类似。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

TERM

当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行

LANG

语言和locale,决定了字符编码以及时间、货币等信息的显示格式

HOME

当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置

getenv函数

获取环境变量值

char *getenv(const char *name);

成功:返回环境变量的值
失败:NULL

setenv函数

设置环境变量的值

int setenv(const char *name, const char *value, int overwrite);

overwrite1 – 覆盖原环境变量
                      0 – 不覆盖。(该参数常用于设置新环境变量,如:ABC = haha-day-night)

unsetenv函数

删除环境变量name的定义

int unsetenv(const char *name);

成功:0
失败:-1
name不存在仍返回0(成功),当name命名为”ABC=“时则会出错

进程控制原语

fork函数

函数原型:

#include <unistd.h>
pid_t fork(void);

返回值有2个:1个进程—>2个进程—>各自对fork做返回
                        1、子进程的pid(非负整数 > 0),父进程
                        2、返回0,子进程
例如:1.创建子进程

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
	pid_t pid;
	printf("xxxxxxxxx\n");
	pid = fork();
	if (pid == -1)
	{
		perror("fork error:");
		exit(1);
	}
	else if(pid == 0)
	{
		printf("I'm child, pid = %u, ppid = %u\n", getpid(), getppid());
	}
	else
	{
		printf("I'm parent, pid = %u, ppid = %u\n", getpid(), getppid());
		sleep(1);
	}
	printf("YYYYYYYYYYYYYY\n");
	return 0;
}

运行结果:
在这里插入图片描述
2.循环创建5个子进程

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
	pid_t pid;
	int i;
	for (i = 0; i < 5; i++)
	{
		pid = fork();
		if (pid == -1)
		{
			perror("fork error");
			exit(1);
		}
		else if(pid == 0)
		{
			break;
		}
	}
	if (i < 5)
	{
		sleep(i);
		printf("%dth child\n", i+1);
	}
	else
	{
		sleep(i);
		printf("I'm parent!\n");
	}
	return 0;
}

运行结果:
在这里插入图片描述
for循环创建子进程,CPU会一下创建出5个子进程,创建完子进程,进程之间互相争夺CPU,先争夺到的CPU,先执行程序
注意,以上中的父进程,其实是bash的的子进程,当在bash中执行程序时,bash从前台换到后台,创建子进程执行程序,当程序执行完后,bash从后台换到前台。

getpid函数

uid_t getpid(void);  // 获取当前进程的进程ID

getppid函数

uid_t getppid(void);  // 获取当前进程的父进程ID

getuid函数

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

getgid函数

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

进程共享

父子相同处:全局变量、.data.text、栈、堆、环境变量、用户ID、宿主目录,进程工作目录、信号处理方式…
父子不同处:1、进程ID,2、fork返回值,3、父进程ID,4、进程运行时间,5、闹钟(定时器),6、未决信号集
父子进程间遵循读时共享写时复制的原则
父子进程共享:1、文件描述符(打开文件的结构体)2、mmap建立的映射区

gdb调试

使用gdb调试的时候,gdb值能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

set follow-fork-mode child  命令设置高蛋白在fork之后跟踪子进程
set follow-fork-mode parent  设置跟踪父进程

注意,一定要在fork函数调用之前设置才有效

exec 函数族

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

execlp函数

加载一个进程,借助PATH环境变量
函数原型:

int execlp(const char *file, const char *arg, ...
					 /* (char  *) NULL */);

返回值:成功,无返回值。失败,返回-1
参数1:要加载的程序的名字,该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
arg[0]为可执行程序名,命令行参数以NULL结尾,需要加上
该函数通常调用系统程序。如:lsdatecpcat等命令

execl函数

加载一个进程,通过 路径+程序名 来加载
函数原型:

int execl(const char *path, const char *arg, ...
				 /* (char  *) NULL */);

返回值:成功,无返回值。失败,返回-1
对比execlp函数,如加载ls命令带有-l参数

execlp("ls", "ls", "-l", NULL);
execl("/bin/ls", "ls", "-l", NULL);

execle函数

加载一个进程,
函数原型:

 int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);

execv函数

加载一个进程,通过 路径+程序名 来加载
函数原型:

int execv(const char *path, char *const argv[]);

execl函数类似,只不过,将参数放在argv[]

execvp函数

加载一个进程,使用PATH环境变量
函数原型:

int execvp(const char *file, const char *argv[]);

execve函数

加载一个进程,使用自定义环境变量env
函数原型:

int execve(const char *path, const char *argv[]);

exec 函数族一般规律

       exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值为-1。所以通常我们直接在exec函数调用后直接调用perror()exit(),无需if判断。

l(list)          命令行参数列表
p(path)          搜索file时使用path变量
v(vector)        使用命令行参数数组
e(environment)   使用环境变量数组,不适用进程原有的环境变量,设置新加载程序运行的环境变量

       事实上,只有execve是真正的系统调用,其他五个函数最终都调用execve
在这里插入图片描述

回收子进程

孤儿进程

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

僵尸进程

       僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程
       僵尸进程不能使,用kill命令清楚掉。因为kill命令只是用来终止进程的,而僵尸进程已经终止

wait函数

功能:
    1、阻塞等待子进程退出
    2、回归子进程残留资源
    3、获取子进程结束状态(退出原因)
函数原型:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

返回值:成功,清理掉的子进程ID。失败,-1(没有子进程)
当进程终止时,操作系统的隐式回收机制:
                     1、关闭所有文件描述符
                     2、释放用户空间分配的内存
                     内核的PCB仍存在,其中保存该进程的退出状态
可用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:

1WIFEXITED(status) 为非0 --------->  进程正常结束
 	WEXITSTATUS(status) 如上宏为真,使用此宏 ------->  获取进程退出状态(exit的参数)
2WIFSIGNALED(status) 为非0 ------->  进程异常终止
	WTERMSIG(status) 如上宏为真,使用此宏 ---------->  获取使进程终止的信号的编号
3WIFSTOPPED(status) 为非0  ------->  进程处于暂停状态
	WSTOPSIG(status) 如上宏为真,使用此宏  --------->  获取使进程终止的暂停的编号
	WIFCONTINUED(status) 为真 ------>  进程暂停后已继续运行

一次wait回收调用,回收一个子进程

waitpid函数

可以指定pid进程清理,可以不阻塞
函数原型:

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

返回值:成功,返回清理掉的子进程ID。失败,-1(无子进程)

参数pid:
	>0  回收指定ID的子进程
	-1  回收任意子进程(相当于wait)
	 0  回收和当前调用waitpid一个组的所有子进程
	<-1 回收指定进程组内的任意子进程

参数options0,表示阻塞。WNOHANG,表示非阻塞,使用轮询的方式回收,返回值为0说明子进程正在运行,返回值为-1说明子进程回收完毕
一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环

IPC方法(进程间通信)

现今常用的进程间通信方式:
           1、管道(使用最简单)
           2、信号(开销小)
           3、共享映射区(无血缘关系)
           4、本地套接字(最稳定)

管道

特质:
    1、本质是一个伪文件(实为内核缓冲区)
    2、由两个文件描述符引用,一个表示读端,一个表示写端
    3、规定数据从管道的写端流入管道,从读端流出
原理:内核使用环形队列机制,借助内核缓冲区(4K)实现
局限性:
    1、数据自己读不能自己写
    2、数据一旦被读走,便不在管道中存在,不可反复读取
    3、由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
    4、只能在有公共祖先的进程间使用管道
常见的通信方式:单工通信、半双工通信,全双工通信

pipe函数

函数原型:

#include <unistd.h>
int pipe(int pipefd[2]);

返回值:成功,0。失败,-1,设置errno
函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定f[0] -> rfd[1] - > w,就像0对应标准输入,1对应标准输出。向管道文件读写数据其实在读写内核缓冲区。
例如:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
	int fd[2];
	int ret = pipe(fd);
	if (ret == -1)
	{
		perror("pipe error");
		exit(1);
	}
	pid_t pid;
	pid = fork();
	if (pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if (pid  == 0)  // 子进程,读数据
	{
		close(fd[1]);
		char buf[1024];
		ret = read(fd[0], buf, sizeof(buf));
		if (ret == 0)
		{
			printf("---\n");
		}
		write(STDOUT_FILENO, buf, ret);
	}
	else                 // 父进程,写数据
	{
		close(fd[0]);
		write(fd[1], "hello pipe\n", strlen("hello pipe\n"));
	}
	return 0;
}

执行结果:
在这里插入图片描述

读管道:管道中有数据,read返回实际读到的字节数
	   管道中无数据,写端全关闭:read返回0
				   仍有写端打开:阻塞等待
写管道:读端全关闭,进程异常终止(SIGPIPE信号)
	   有读端打开,管道未满:写数据,返回写入字节数
				 管道已满:阻塞(少见)

mkfifo函数

函数原型:

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char * pathname,mode_t mode);

返回值:成功,0。失败,-1,设置errno
参数pathname,想要创建的FIFO路径,
参数mode,创建的FIFO访问模式。这个访问会与当前进程的umask进程运算,以产生实际应用的权限模式。

共享存储

函数原型:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

返回值:成功,返回创建的映射区首地址。失败:MAP_FAILED宏

参数:
addr:  建立映射区的首地址,由linux内核指定。使用时,直接传递NULL
length:欲创建映射区的大小
prot:  映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
		MAP_SHARED:会将映射区所作操作反映到物理设备(磁盘)上
		MAP_PRIVATE:映射区所作的修改不会反映到物理设备
fd:    用来建立映射区的文件描述符
offset:映射文件的偏移(4k的整数倍)

例如:

#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
	int fd = open("english.txt", O_RDWR, 0644);
	if (fd < 0)
	{
		perror("open error");
		exit(1);
	}
	// 创建新文件一定先要拓展空间,否则文件大小为0,会报总线错误
	// 即文件大小为0时,不能创建映射区
	int len = ftruncate(fd, 4);
	if (len == -1)
	{
		perror("ftruncate error");
		exit(1);
	}
	char *p = NULL;
	p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED)
	{
		perror("mmap error");
		exit(1);
	}
	strcpy(p, "ab\n");
	int ret = munmap(p, 4);
	if (ret == -1)
	{
		perror("munmap error");
		exit(1);
	}
	close(fd);
	return 0;
}

结果:
在这里插入图片描述

注意事项:
	1、可以创建大小为0的堆区,并且也能释放
	2、不能创建大小为0的映射区。
	3、创建映射区的地址与释放映射区的地址必须相同
	4、创建映射区的权限要小于等于打开文件权限,映射区创建过程中隐含着对文件的一次读操作
		1、只读方式打开文件,是不可以使用读写方式操作映射区
		2、只写方式打开文件,是不可以使用写方式操作映射区
	5、偏移量只能是4k的整数倍
	6、必须检查mmap的返回值
	7、先关闭文件描述符,对mmap映射没有影响

父子进程间通信

当映射区标志位参数flagsMAP_SHARED时,父子进程共享映射区

#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
	int var = 0;
	int fd = open("english.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
	if (fd < 0)
	{
		perror("open error");
		exit(1);
	}
	unlink("english.txt");   // 删除临时文件目录项,使之具备被释放条件
	int len = ftruncate(fd, 4);
	if (len == -1)
	{
		perror("ftruncate error");
		exit(1);
	}
	int *p = NULL;
	p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED)
	{
		perror("mmap error");
		exit(1);
	}
	close(fd);
	pid_t pid;
	pid = fork();
	if (pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if (pid == 0)
	{
		*p = 1000;
		var = 2000;
		printf("child process, *p = %d, var = %d\n", *p, var);
	}
	else
	{
		sleep(1);
		printf("parent process, *p = %d, var = %d\n", *p, var);	
		wait(NULL);
		int ret = munmap(p, 4);
		if (ret == -1)
		{
			perror("munmap error");
			exit(1);
		}
	}
	return 0;
}

结果:
在这里插入图片描述
当映射区标志位参数flagsMAP_PRIVATE时,父子进程独享映射区

#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
	int var = 0;
	int fd = open("english.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
	if (fd < 0)
	{
		perror("open error");
		exit(1);
	}
	unlink("english.txt");   // 删除临时文件目录项,使之具备被释放条件
	int len = ftruncate(fd, 4);
	if (len == -1)
	{
		perror("ftruncate error");
		exit(1);
	}
	int *p = NULL;
	p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED)
	{
		perror("mmap error");
		exit(1);
	}
	close(fd);
	pid_t pid;
	pid = fork();
	if (pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if (pid == 0)
	{
		*p = 1000;
		var = 2000;
		printf("child process, *p = %d, var = %d\n", *p, var);
	}
	else
	{
		sleep(1);
		printf("parent process, *p = %d, var = %d\n", *p, var);	
		wait(NULL);
		int ret = munmap(p, 4);
		if (ret == -1)
		{
			perror("munmap error");
			exit(1);
		}
	}
	return 0;
}

结果:
在这里插入图片描述

匿名映射区

无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定,使用MAP_ANONYMOUS(或MAP_ANON),例:

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

注意:宏MAP_ANONYMOUS(或MAP_ANON)只能再Linux操作系统下使用
类unix系统Linux系统下创建匿名映射区:

fd = open("/dev/zero", O_RDWR);
p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

非血缘关系进程间mmap通信

必须使用同一个文件MAP_SHARED
strace 可执行程序追踪当前程序执行中所使用的系统调用

信号

特点:1、简单
           2、不携带大量信息
           3、满足某个特设条件才发送

信号的机制

特质:信号都是通过软件方法实现,导致信号有很强的延时性
每个进程收到的所有信号,都是由内核负责发送的,内核处理。

与信号相关的事件和状态

产生信号
     1、按键产生
     2、系统调用产生
     3、软件条件产生
     4、硬件异常产生
     5、命令产生
递达:递送并且到达进程
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态
信号处理方式
     1、执行默认动作
     2、忽略(丢弃)
     3、捕捉(调用户处理函数)
阻塞信号集(信号屏蔽字):将某些信号加入集合,对它们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将退后(接触屏蔽后)
未决信号集
     1、信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂
     2、信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态

信号 4 要素

1、编号
2、名称
3、事件
4、默认处理动作
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

man 7 signal

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
SIGILL        4       Core    Illegal Instruction
SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no
                              readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated
SIGCONT   19,18,25    Cont    Continue if stopped
SIGSTOP   17,19,23    Stop    Stop process
SIGTSTP   18,20,24    Stop    Stop typed at terminal
SIGTTIN   21,21,26    Stop    Terminal input for background process
SIGTTOU   22,22,27    Stop    Terminal output for background process

默认动作:
     Term:终止进程
     lgn:忽略信号(默认即时对该种信号忽略操作)
     Core:终止进程,生成Core文件。(查验进程死亡原因,用于gdb调试)
     Stop:停止(暂停)进程
     Cont:继续运行进程

信号的产生

终端按键产生信号

Ctrl + c ----> 2) SIGINT (终止/中断)  "INT"----Interrupt
Ctrl + z ----> 20) SIGTSTP (暂停/终止)  "T" ----Terminal终端
Ctrl + \ ----> 3) SIGQUIT (退出)

硬件异常产生信号

除0操作  ----> 8) SIGFPE (浮点数例外)  "F"----float 浮点数
非法访问内存  ----> 11) SIGSEGV (段错误)
总线错误  ----> 7) SIGBUS

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:发送给进程有权限发送的系统中所有进程

raise和abort函数

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

软件条件产生信号

alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止
每个进程都有且只有唯一一个定时器
unsigned int alarm(unsigned int seconds);返回值:0或剩余的秒数,无失败
使用time命令查看程序执行时间
实际执行时间 = 系统时间 + 用户时间 + 等待时间

setitimer函数

设置定时器(闹钟)。可代替alarm函数。精度微秒us,可以实现周期定时

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

返回值:成功,0。失败,-1,设置errno

参数:which:指定定时方式
	1.自然定时:ITIMER_REAL   -----> 14) SIGLARM             计算自然时间
	2.虚拟空间计时(用户空间):ITIMER_VIRTUAL  ----> 26) SIGVTALRM  只计算进程占用CPU时间
	3.运行时计时(用户+内核):ITIMER_PROF  ----> 27) SIGPROF   计算占用CPU及执行系统调用的时间
struct itimerval {
	struct timerval it_interval;
	struct timerval it_value;
};
struct timerval {
	time_t tv_sec;
	suseconds tv_usec;
};
struct itimerval {
	struct timerval {
		it_value.tv_sec;
		it_value.tv_usec;
	}it_interval;
	
	struct timerval {
		it_value.tv_sec;
		it_value.tv_usec;
	}it_value;
}it, oldit;
提示: it_interval,用来设定两次定时任务之间间隔的时间
	  it_value,定时的时长
	  两个参数都设置为0,即清0操作
signal函数

捕获信号,并且执行函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

例如:

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void myfunc(int signo)
{
	printf("hello world\n");
}
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;
	}
	while(1);
	return 0;
}

结果:
在这里插入图片描述

信号集操作函数

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

信号集设定

sigset_t set;
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类型的本质时位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效

sigprocmask函数

用来屏蔽信号解除屏蔽也使用该函数。
本质,读取或修改进程的信号屏蔽字(PCB中)
屏蔽信号:只是将信号处理延后执行(延至接触屏蔽);而忽略表示将信号丢处理
9号信号和19号信号是不允许设置屏蔽的

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

返回值:成功,0。失败:-1,设置errno

参数:
	set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号
	oldset:传出参数,保存旧的信号屏蔽集
	how:假设当前的信号屏蔽字位mask
		1. SIG_BLOCK:set表示需屏蔽的信号。相当于mask = mask|set
		2. SIG_UNBLOCK:set表示需解除屏蔽的信号。相当于mask = mask &~set
		3. SIG_SETMASK:set表示用于替代原始屏蔽集的新屏蔽集。相当于mask = set
			若调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回
			前,至少将其中一个信号递达

sigpending函数

读取当前进程的未决信号集

int sigpending(sigset_t *set);

set传出参数
返回值:成功,0;失败,-1,设置errno

信号捕捉

signal函数

捕获信号,并且执行函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

返回值:成功,当前的signal handler值。失败,SIG_ERR

sigaction函数

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

返回值:成功,0。失败,-1
参数:
     act:传入参数,新的处理方式
     oldact:传出参数,旧的处理方式

struct sigaction {
	void (*sa_handler)(int);
	void (*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t sa_mask;  // 用于信号捕捉函数期间所屏蔽的信号集
	int sa_flags;   // 传递来的标志位,默认值0,信号捕捉函数执行期间,自动屏蔽本信号
	void (*sa_restorer)(void);
};

sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素(弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序(很少使用)
重点掌握:
     1.sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略或SIG_DFL表执行默认动作
     2.sa_mask:调用信号处理函数时,所需屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置
     3.sa_flags:通常设置为0,表使用默认属性

信号捕捉特性

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

内核实现信号捕捉过程:

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值