文章目录
三、系统(环境)编程
1、进程控制
1.程序进程
程序:编译好的二进制文件
进程:执行着的程序
- 程序员角度:运行一系列指令的过程
- 操作系统角度: 分配系统资源的基本单位
区别:- 程序占用磁盘,不占用系统资源
- 进程占用系统资源
- 一个程序对应多个进程
- 一个进程只能对应一个程序
- 进程有生命周期
单道程序(DOS系统)
进程的状态转化
cpu和mmu(memory manage units)
mmu:
- 1.控制内存到物理地址的映射,(虚拟出更大的内存)
- 2.设置内存的访问级别(用户空间,内核空间)
- 用户空间:虚拟地址即使是同一块,也是映射到不同的物理空间(银行账户)
- 内核空间映射到同一块(进程通信)
2.查看PCB
txdy827@ubuntu:~$ sudo grep -rn "struct task_struct {" /usr/
[sudo] txdy827 的密码:
#结果:/usr/src/linux-headers-4.15.0-111/include/linux/sched.h:560:struct task_struct {
然后vim 这个文件
/struct task_struct { //查找到640行,然后光标定在{符号上 使用 % 跳转到结构体结束的位置
结构体内容:
- 进程id:系统每个进程有唯一的id,在C语言中用pid_t 类型表示,其实就是一个非负整数
- 进程状态,就绪,运行,挂起,停止等
- 进程切换时需要保存和恢复的一些CPU寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前的工作目录,每个进程一个独立的工作目录
- umasK掩码
- 文件描述符表,包含很多指向file结构体的指针
- 和信号相关的信息
- 用户ID和组ID
- 会话(session)和进程组
- 进程可以使用的资源上线
txdy827@ubuntu:~$ ulimit -a // 查看资源上限
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15511
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15511
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
3.环境变量
写法: key=value 没有空格
命令:env 查看
PATH : 可执行文件的搜索路径
SHELL:当前shell,通常是/bin/bash
TERM:显示终端类型
LANG:语言货币编码时间
HOME:家目录
配置文件: hello.conf
一般放在/etc
/home/pi/etc
函数:获取环境变量
#include <stdlib.h>
char *getenv(const char *name);
eg:
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("home is [%s]\n",getenv("HOME"));
return 0;
}
#
txdy827@ubuntu:~/test$ ./a.out
home is [/home/txdy827]
添加一个环境变量:
在~/.bashrc文件中:
export key=val
就好了
4.创建一个新的进程
如果创建父子进程时候,父进程先死了,子进程还没死,则会出现,shell 进程先把自己的进程输出,之后刚才创建的子进程又打印一遍输出,操作系统设计就是只管下一层,当创建的父进程(是shell进程的子进程)死了,shell进程就返回了。
如:
I am a father,I will die!
txdy827@ubuntu:~/test$ I am a child,pid is 2682,ppid is 1234
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
- 失败-1
- 成功:两次返回
- 父进程返回子进程id
- 子进程返回0
获取进程id
#include <sys/types.h>
#include <unistd.h>
pid_getpid(void);//获得pid,当前进程id,
pid_getppid(void);// 获取父进程id
eg
注意,如果创建子进程之前没有\n刷新缓冲区,则会在创建子进程的时候,把子进程会把刚才那个begin—一起创建出来。
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("begin----------------\n");
pid_t pid=fork();
if(pid<0)
{
perror("fork err");
exit(1);
}
if(pid==0)
{
printf("I am a child, pid is %d,ppid is %d\n",getpid(),getppid());
}// if(pid>0)
else
{
printf("I am a parent,child_pid is %d, my_pid is %d,ppid is %d\n",pid,getpid(),getppid());
sleep(1); //让父进程等会再死,不然就子进程变成孤儿进程被领养了
}
printf("end--------------\n");
return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
begin----------------
I am a parent,child_pid is 3363, my_pid is 3362,ppid is 1853
I am a child, pid is 3363,ppid is 3362
end--------------
end--------------
进到一个源文件,在esc之下 输入:,然后 vsp 再一个文件就分屏显示了
我们写的进程的父进程是shell(-bash)进程
最初的父进程是 init进程1号进程
ps aux
ps ajx 追溯血缘关系
杀死进程
kill -9 2980 //杀死2980号进程
kill -l 查看kill信息
SIGSEGV 段错误
SIGBUS 总线错误
SIGFPE 古典错误,除0错误
5.创建多个子进程
grep -v grep :排除关键字grep
创建5gg额子进程
#include<stdio.h>
#include<stdlib.h>
int main()
{
int i=0;
int n=5;
pid_t pid=0;
for(i=0;i<5;i++)
{
pid = fork();
if(pid==0)
{
printf("I am a son pid is %d,ppid is %d\n",getpid(),getppid());
break; //子进程退出的口
}
else
{
am a father pid is %d,ppid is %d\n",getpid(),getppid());
}
}
while(1){
sleep(1);
}
return 0;
}
eg2创建5个子进程并按照顺序退出
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int i=0;
int n=5;
pid_t pid=0;
for(i=0;i<5;i++)
{
pid = fork();
if(pid==0)
{
// printf("I am a son pid is %d,ppid is %d\n",getpid(),getppid());
break;
}
else
{
// printf("I am a father pid is %d,ppid is %d\n",getpid(),getppid());
}
}
sleep(i);
if(i<5) printf("I am a child,pid is %d,ppid is %d\n",getpid(),getppid());
else
printf("I an a patern,pid is%d,ppid is %d\n",getpid(),getppid());
return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
I am a child,pid is 2260,ppid is 2259
I am a child,pid is 2261,ppid is 2259
I am a child,pid is 2262,ppid is 2259
I am a child,pid is 2263,ppid is 2259
I am a child,pid is 2264,ppid is 2259
I an a patern,pid is2259,ppid is 1659
6.父子进程关系
刚fork之后,
- 父子相同处:
- 全局变量,.data,.text,栈,堆,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式相同
- 不同之处:
- 进程ID,fork返回值,父进程id不同,进程运行时间不同,闹钟(定时器)不同,未决信号集不同
一开始设计是类似复制从0-3G的内存,现在是读时共享,写时复制
- 进程ID,fork返回值,父进程id不同,进程运行时间不同,闹钟(定时器)不同,未决信号集不同
7.exec族函数
执行其他程序
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL */);
//执行程序的时候,使用PATH环境变量,执行程序时不用加路径
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
- file 要执行的程序
- arg 参数列表
- 参数列表最后一个需要一个NULL最为结尾,哨兵
- 参数从argv[0]开始,用要执行的文件名占位,作为第二个参数
- 返回值
- 只有失败才返回
eg:
- 只有失败才返回
#include<unistd.h>
#include<stdio.h>
int main()
{
execl("/bin/ls","ls","-l","--color=auto",NULL);
execlp("ls","ls","-l","--color=auto",NULL); //第二个参数,用要执行的文件名占位置
perror("exec err\n");
return 0;
}
#结果:
tu:~/test$ ./a.out
总用量 56
-rw-r--r-- 1 txdy827 txdy827 132 7月 29 00:25 06_execlp.c
-rwxr-xr-x 1 txdy827 txdy827 8352 7月 29 00:25 a.out
~
查看显示的绿色
txdy827@ubuntu:~/test$ alias ls
alias ls=‘ls --color=auto’
调用关系:
shell执行ls等操作,,ls这种程序不出错不返回,所以shell程序,在执行这些之前execl族函数之前先fork()生成一个子进程去执行,然后自己接着自己的做,
8.孤儿进程和僵尸进程
孤儿进程:父亲死了,子进程被init进程领养
僵死进程:子进程死了,父进程没有回收子进程的资源(PCB)
如何回收僵死进程的资源? 杀死父亲,init领养,负责回收
eg:孤儿进程:
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid==0)
{
while(1){
printf("I am a child,pid is %d,ppid is %d\n",getpid(),getppid());
sleep(2);
}
}else
{
printf("I am a parent,pid is %d,ppid is %d\n",getpid(),getppid());
sleep(4);
printf("I am a father,I will die!\n");
}
return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
I am a parent,pid is 2681,ppid is 1698
I am a child,pid is 2682,ppid is 2681
I am a child,pid is 2682,ppid is 2681
I am a father,I will die!
txdy827@ubuntu:~/test$ I am a child,pid is 2682,ppid is 1234
I am a child,pid is 2682,ppid is 1234
I am a child,pid is 2682,ppid is 1234
9.wait
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
作用:
- 阻塞等待
- 回收子进程资源
- 查看死亡原因
pid_t wait(int * status); - status 传出参数
- 返回值:
- 成功返回:种植的子进程ID
- 失败返回-1
子进程的死亡原因:
- 正常死亡 WIFEXITED
- 如果WIFEXITED为真,使用WEXITSTATUS得到退出状态
- 非正常死亡 WIFSIGNALED
- 如果 WIFSIGNALED为真,使用 WTERMSIG得到信号,知道s哈原因死了
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid=fork();
if(pid==0) // 子进程的逻辑
{
printf("I am a child,I will die");
sleep(2);
return 101;
//exit(102); 也行,头文件加<stdlib.h>
}
else if(pid>0) //父进程的逻辑
{
printf("I am a parents,wait child to die\n");
int status; // 定义一个数字状态来记录子进程死亡的原因
pid_t wpid=wait(&status); //获取死亡原因,保存到status中
printf("wait is ok,wpid is %d,pid is %d\n",wpid,pid);
if(WIFEXITED(status)) // 正常死亡
{
printf("child exit with %d\n",WEXITSTATUS(status));//显示退出那个101
}
if(WIFSIGNALED(status)) // 非正常死亡
{
printf("child killed by %d\n",WTERMSIG(status)); // 寻找原因
}
while(1) //在父进程的{}中,不让父进程死了
{
sleep(1);
}
}
return 0;
}
测试非正常死亡,只修改子进程这一点,其他不变,然后生成可执行文件,执行,然后再另外一个窗口用ps aux | grep a.out查看进程序号,然后kill -9 3545,把子进程给杀死,就会在原窗口看到非正常死亡的原因了。
int main()
{
pid_t pid=fork();
if(pid==0)
{
printf("I am a child,I will die");
while(1)
{
printf("guo lai da wo\n");
sleep(2);
// return 101;
//exit(102);
}
}
父进程和子进程共享文件描述符表,读写控制位置也共享父进程子进程读写才没有覆盖,
10.
2、进程间通信ipc
1、ipc
Inter Process Communication:进程间通信,通过内核的缓冲区进行数据交换的机制
IPC通信方式:
- pipe:管道,最简单,有血缘才
- fifo:有名管道
- mmap:文件映射共享IO,–速度最快,(把文件映射到内存,直接操作内存)
- 本地socket 最稳定
- 信号 携带信息量最小
- 共享内存 :使用特定API申请的一块内存区域,大家都可以用
- 消息队列
2.管道:
- 从两个进程角度看:是半双工,
- 从管道本身来看只能一边读一边写,所以有人说是:单工
管道实现:通过内核创建一个伪文件:
只能是有血缘关系才能通信,因为fork之后共享这两个文件描述符,其他进程根本找不到这两个文件描述符。
01图片34444
函数:
#include <unistd.h>
int pipe(int pipefd[2]); //调用之后相当于在内核开辟了一个缓冲区
- pipefd[2] 读写文件描述符,1:写 0:读
- 返回值:0 成功 ,失败返回-1
eg1、子进程写,父进程读:
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2]; //开辟一个数组,两个元素是文件描述符,一个代表读(0),一个代表写(1),
pipe(fd);//pipe函数初始化,创建两个元素是文件描述符,调用pipe函数之后相当于在内核区开辟了一个缓冲区,一端写,一端读
pid_t pid=fork();
if(pid==0)
{
//son
write(fd[1],"hello",5);
}else if(pid>0)
{
char buf[12]={0};
int ret=read(fd[0],buf,sizeof(buf));
if(ret>0)
write(STDOUT_FILENO,buf,ret);
}
return 0;
}
eg2、实现ps -aux | grep bash命令
理解:标准输入,标准输出都在屏幕的左边
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int fd[2];
pipe(fd); //调用此函数,就在内核缓冲区开辟了一片内存。
pid_t pid=fork();
if(pid==0)
{
//son
//son ---> ps
//1 先重定向,标准输出重定向到管道的写一端,标准输出的内容,直接写道管道中,输出到屏幕-->写入管道
dup2(fd[1],STDOUT_FILENO);
// 2 execlp 执行ps命令
execlp("ps","ps","aux",NULL);
}else if(pid>0)
{
//father
//重定向,标准输入重定向到管道的读一端,将要在屏幕输入的内容直接定向到管道读的一端,输入的内容直接读出
dup2(fd[0],STDIN_FILENO);
execlp("grep","grep","bash",NULL);
}
return 0;
}
#问题:
# 管道的使用,虽然子进程把内容写给了父进程,但是父进程还掌握着管道的写端,所以由于管道的特性,父进程就会阻塞等待,而不会退出
解决办法:
把管道的使用细致化,父、子进程只掌握管道的一端
在子进程模块把读端关闭
close(fd[0]);
在父进程模块close(fd[1]);
读管道:
- 写端全部关闭,—read 读到0,相当于读到文件末尾
- 写端没有全部关闭
- 有数据–read读到数据
- 没有数据–read阻塞 fcntl 函数可以更改非阻塞
写管道:
- 读端全部关闭—产生一个信号SIGPIPE,程序异常终止
- 读管道没有全部关闭
- 管道已满:–write阻塞
- 管道未满—write正常写入
命令ulimit -a,可以看到管道大小 521 * 8(而管道容量会更大一些,写满管道,能写的数据回会比这个大小更大)
- 管道优点:
- 简单
- 缺点
- 只能父子进程间通信,单方向,如果需要双方向通信,需要创建两根管道
3.fifo通信
fifo有名管道,实现无血缘关系进程通信
- 创建一个管道的伪文件
- 命令:mkfifo myfifo
- 函数: int mkfifo(const char *pathname, mode_t mode);
- 内核会针对fifo文件开辟一个缓冲区,操作fifo文件,可以操作缓冲区,实现建进程间通信,实际上就是文件读写
先mkfifo myfifo创建一个fifo文件,
myfifo_w.c写文件:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char * argv[])
{
if(argc!=2)
{
printf("./a.out fifomane\n");
return -1;
}
//当前目录有一个myfifo文件,打开fifo文件
int fd=open(argv[1],O_WRONLY);
char buf[256];
int num=1;
while(1) //循环写
{
memset(buf,0x00,sizeof(buf));
sprintf(buf,"xiaoming%4d",num++);//先在缓冲区内写,
write(fd,buf,strlen(buf));//再把缓冲区里边的写进fifo文件中
sleep(1);
}
return 0;
}
myfifo_r.c 读文件;
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char * argv[])
{
if(argc!=2)
{
printf("./a.out fifomane\n");
return -1;
}
int fd=open(argv[1],O_RDONLY);
char buf[256];
int ret;
while(1){
ret=read(fd,buf,sizeof(buf));
if(ret>0)
{
printf("read:%s\n",buf);
}
}
close(fd);
return 0;
}
读出来fifo里边就没数据了
读端都关闭,写端自动关闭
fifo注意事项:读写两端都开才行
- 打开fifo文件时,read端会阻塞等待write端打开,write端也会等待另外一端打开
4.mmap
函数原型
#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);
图34440001
创建映射区,
- addr:传NULL
- length:映射区长度
- prot
- PROT_READ:可读
- PROT_WRITE:可写
- flags
- MAP_SHARED:共享的 (对内存修改会影响源文件)
- MAP_PRIVATE:私有的
- fd文件描述符,open打开一个文件
- offset:偏移量(起始值)
- 返回值
- 成功 返回 可用的内存首地址
- 失败 返回 MAP_FALED(-1)
int munmap(void *addr, size_t length);
- addr:传mmap的返回值
- length创建的长度
- 返回值:成功返回0,失败-1
eg.开启一个文件xxx.txt,并映射到内存中去,在内存中写入hello然后自动更新到文件中
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd=open("xxx.txt",O_RDWR);
//映射到内存中
char * mem=mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(mem==MAP_FAILED){
perror("MAP err");
return -1;
}
// close(fd);
//内存中字符串拷贝
strcpy(mem,"hello");
munmap(mem,8);
//release
close(fd);
return 0;
}
mmap问题
1.如果更改mem变量的地址,释放的时候munmap,传入的men还能成功吗?
不能。
2、如果对men越界操作会怎么样?
文件大小对映射区有影响,尽量避免。文件大,映射区开的小,写入内存中的字符串会正常映射过来,但是如果文件小,没有映射区大,则内存中的操作(字符串拷贝,就不能完全拷贝)会做不完。
3、如果文件偏移量随便填一个数会怎么样?
不行。offset必须是4K的整数倍(0,4096),一个文件的最小门槛时4096(8个512)
4、如果文件描述符先关闭,对mmap映射有没有影响?
没有影响。映射之后,通道已经通了。
5、open的时候,可以新创建一个文件来创建映射区吗?
不可以用大小为0的文件来映射,open一个新文件,需要给这个文件扩展非0容量之后就可以了。
6、open文件选择O_WRONLY,可以吗?
不可以,Permission denied 映射到内存操作,(隐藏一次读操作,先把文件读一下,再映射到内存)
7、当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
不可以。Permission denied 当为share时,open开的文件要多于等于内存要操作的权限。
8、mmap什么时候会报错?
上面很多情况都会报错。
9、不判断返回值会怎么样?
会死的很难看。
eg,实现父子进程通信
不过此处实现需要先指定一个文件,不好,后边给出匿名映射,
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/mman.h>
int main()
{
int fd=open("xxx.txt",O_RDWR);
int *mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(mem==MAP_FAILED)
{
perror("mmap err");
return -1;
}
pid_t pid=fork();
if(pid==0)
{
//子进程
*mem=100;
printf("child ,*mem=%d\n",*mem);
sleep(4);
printf("child ,*mem=%d\n",*mem);
}else if(pid>0)
{
sleep(1);
printf("parent ,*mem =%d\n",*mem);
*mem=1001;
sleep(1);
printf("parent ,*mem =%d\n",*mem);
sleep(4);//是为了等待儿子先死,或者直接使用后边一句,不然就会父进程先死,子进程最后那个输出会在shell进程之后输出的
wait(NULL);
}
munmap(mem,4);
close(fd);
return 0;
}
#结果:
txdy827@ubuntu:~/test/ipc$ ./05_share_child
child ,*mem=100
parent ,*mem =100
parent ,*mem =1001
child ,*mem=1001
匿名映射:
MAP_ANON
Synonym for MAP_ANONYMOUS. Deprecated.
MAP_ANONYMOUS
// int fd=open("xxx.txt",O_RDWR);
int *mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//第4个参数增加宏,d
// close(fd);
5.
3、信号
1、简介:
信号的特点:简单,不能带大量信息,满足特定条件发生
信号的机制:进程B发送给进程A,内核产生信号,内核处理
信号的产生:
- 按键产生 ctrl+c,ctrl+z,ctrl+\
- 调用函数 kill raise abort
- 定时器:alarm,setitimer
- 命令产生kill
- 硬件异常,段错误、浮点错误、总线错误、SIGPIPE
信号的状态: - 产生
- 递达:信号到达并处理完
- 未决:信号被阻塞了
信号的默认处理方式: - 忽略
- 执行默认动作
- 捕获
信号4要素 - 编号
- 事件
- 名称
- 默认处理动作
- 忽略
- 终止
- 终止+core
- 暂停
- 继续
信号也叫软中断:软件产生的中断。有可能会有延迟
9号19号信号不能捕获,不能忽略,不能阻塞
阻塞信号集和未决信号集
图555555555555555
终端按键产生信号
* ctrl+C 2号信号 终止中断 keyboard interrupt
* ctrl + Z 20号信号,暂停/停止
* ctrl+\ 3号信号sigquit 退出
硬件异常产生信号
* 除0,
* 非法访问内存—>段错误
* 总线错误
kill函数,命令产生信号
系统API
int kill(pid_t pid,int sig);
* pid>0:要发送进程ID
* pid==0代表当前调用进程组内所有进程
* pid=-1代表有权限发送的所有进程
* pid<0代表-pid对应的组内所有进程
sig—>对应的信号
eg1,3号子进程传递信号,杀死父进程
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
int main()
{
int i;
for(i=0;i<5;i++) //循环创建子进程
{
pid_t pid=fork();
if(pid==0) //如果是子进程就推出去
break;
}
if(i==2){ //3号子进程刺杀任务
printf("I will kill father after 5 min\n");
sleep(5);
kill(getppid(),SIGKILL);
while(1)
{
sleep(1);
}
}else if(i==5)
{
while(1)
{
printf("I am parent,very happy\n");
sleep(1);
}
}
return 0;
}
eg2杀死3号子进程
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
int main()
{
int i;
int pid3;
for(i=0;i<5;i++){
pid_t pid=fork();
if(pid==0){
break;
}
if(i==2){ //保存5号进程的ID
pid3=pid;
}
}
//if(pid==0) //不能用作判断
if(i<5)
{
while(1)
{
printf("I am child,my pid = %d,ppid is%d\n",getpid(),getppid());
sleep(5);
}
}
//if(pid>0){
if(i==5){
sleep(1);
printf("I will kill 3 pro after 5 s\n");
kill(pid3,SIGKILL);
while(1)
{
sleep(1);
}
}
return 0;
}
raise(sig);
abort();
进程自杀:
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
printf("I will kill myself!\n");
sleep(2);
//raise(SIGKILL);
abort();
return 0;
}
2、时钟信号
alarm:
- 定时给自己发送SIGALRM
- 几秒后发送信号
- 返回值,上次闹钟剩余的秒数
- 特别的:如果传入秒数为0,代表取消闹钟
#include<stdio.h>
#include<unistd.h>
int main()
{
int ret=0;
ret=alarm(6); // ret=6秒
printf("ret is %d\n",ret);
sleep(3);
ret=alarm(5); // ret =3秒
printf("ret after is %d\n",ret);
while(1)
{
printf("la da wo ya!\n");
sleep(1);
}
return 0;
}
//代码解释:ret返回第一次剩余的秒数6-3==3,重新调用alarm相当于给他续了更多5秒信号发送的等待时间
3、setitimer
周期性的发送信号
//头文件:
#include <sys/time.h>
//函数原型:
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /* 周期性的时间设置*/
struct timeval it_value; /* 下次的闹钟时间 */
};
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
which
* ITIMER_REAL 自然定时法 SIGALRM
* ITIMER_VIRTUAL 计算进程的执行时间 SIGVTALRM
* ITIMER_PROF 进程的执行时间+调度时间 ITIMER_VIRTUAL
new_value 要设置的闹钟时间
*
old_value 原闹钟时间
可以当成alarm使用
#include<unistd.h>
#include<sys/time.h>
#include<stdio.h>
int main()
{
struct itimerval myit={{0,0},{3,0}};
setitimer(ITIMER_REAL,&myit,NULL);
while(1){
printf("who can kill me!\n");
sleep(1);
}
return 0;
}
捕捉
#include <signal.h>
typedef void (*sighandler_t)(int); // 函数指针,函数void类型,参数int,定义了一个函数原型的声明
sighandler_t signal(int signum, sighandler_t handler);
#include<unistd.h>
#include<sys/time.h>
#include<stdio.h>
#include<signal.h>
void catch_sig(int num) // 函数指针,和signal函数配套使用
{
printf("cat %d sig \n",num);
}
int main()
{
signal(SIGALRM,catch_sig); //注册一下:如果出现了SIGNAL信号,就调用后边这个函数
struct itimerval myit={{3,0},{5,0}};
setitimer(ITIMER_REAL,&myit,NULL);
while(1){
printf("who can kill me!\n");
sleep(1);
}
return 0;
}
4、信号集处理函数
清空信号集
int sigemptyset(sigset_t * set);
填充信号集
int sigfillset(sigset_t * set);
添加某个信号到信号集
int sigaddset(sigset_t * set,int signum);
从集合中删除某个信号
int sigdelset(sigset_t * set,int signum);
是否为集合中的成员
int sigismember(const sigset_t * set,int signum);
只有
sigismember()返回值成功时1,失败是0
其他都是成功返回0,失败返回-1
设置阻塞或者解除阻塞信号集
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);
- how
- SIG_BLOCK:设置阻塞
- SIG_UNBLOCK 解除阻塞
- SIG_SETMASK:设置set为新的阻塞集信号
- set传入的信号集
- oldset旧的信号集,传出参数
获取未决信号集
int sigpending(sigset_t * set);
14 打印当前进程所有未决信号集
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4
5 int main()
6 {
7 sigset_t pend,sigproc;//声明两个信号集,第一个是要打印出来的,第二个是阻塞信号集
8 sigemptyset(&sigproc);//设置阻塞信号,等待按键产生信号
9 sigaddset(&sigproc,SIGINT);//把ctrl+c信号添加到这个信号集中,把这个信号添加之后,手动ctrl+c并不能终止进程
sigaddset(&sigproc,SIGQUIT);//添加ctrl+\到阻塞信号集,手动ctrl+\也不能终止程序
sigaddset(&sigproc,SIGKILL);//添加-9kill杀死进程到阻塞信号集,不过这个信号即使添加到阻塞信号集了,这个信号也不能被阻塞。
10 //设置 阻塞 信号集
11 sigprocmask(SIG_BLOCK,&sigproc,NULL);
循环获取未决信号集
12 while(1){
13
14 sigpending(&pend);//获取所有信号
15 int i=0;
16 for(i=1;i<32;i++)
17 {
18 if(sigismember(&pend,i)==1){
19
20 printf("1");
21 }
22 else{
23 printf("0");
24 }
25 }
26 printf("\n");
27 }
return 0;
29 }
#结果:
0110000000000000000000000000000 //ctrl+c是2号信号,被捕获到阻塞信号集,ctrl+\是3号信号,被捕获,
0110000000000000000000000000000//但是kill -9信号不能被捕获,再另一个窗口ps aux,找到进程然后杀死
已杀死
5、信号捕捉
防止进程意外死亡
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
* signum:要捕捉的信号
* handler:要执行的捕捉函数的指针,函数应该声明void fun(int)
另外一个函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum:要捕捉的信号
act:传入的动作
oldact:原动作
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); //执行捕捉函数,临时屏蔽的一个信号集
sigset_t sa_mask;
int sa_flags; // 一般填0或者 SA_SIGINFO
void (*sa_restorer)(void); //无效
};
6、借助SIGCHLD信号实现子进程回收
子进程运行结束,父进程会收到一个SIGCHLD的信号,默认忽略,
7、
4、守护进程
进程组:父进程