上一节的一点尾巴: 使用alarm+pause实现sleep功能
pause的作用是将内核挂起,减小资源使用。但是当接受到信号的时候就会被唤醒。
alarm(3);
pause();
等同于sleep(3);
alarm(3);
signal(SIGALRM , SIG_IGN); //SIGNAL_ignore:处理方式,忽略这个信号
pause();
等同于不占用资源的while(1)
5.1 什么是信号
正式的程序都会接触信号的,我们学到这些内容是比较重要的。
学习一下api,并且理解信号。
【信号】信号是 内容受限的一种异步通信机制。 内容受限:只能发信号,不可以发字符、文字、图像等
信号的目的:用于通信。
信号的特点:异步性。
信号的本质:是int型数字编号。
信号给人的感觉像是 软件中断。发什么信号,怎么发,谁发,是完全不知道的。
信号的举例:
用户发出:CTRL+C kill 按键
内核发出:硬件发生异常后(比如除以0导致出错),操作系统内核发出信号
条件满足发出:如alarm时间到产生SIGALARM信号。管道读端关闭,尝试写会产生SIGPIPE信号
信号的接受者处理方法:
忽略信号
捕获信号 信号绑定一个函数
默认处理 当前进程没有明显的管这个函数,忽略信号或者终止进程
5.2常用信号
系统定义好的信号:
SIGINT 2 重要!CTRL+C送给前台的每个进程 Signal interrupt
SIGABRT 6 调用abort函数,进程异常终止
SIGPOLL 8
SIGIO 8 重要!指示一个异步事件 高级io提及
SIGKILL 9 重要!KILL -9 ,杀死一个进程的终极方法,他不能被拦截,但需要一定的权限。
SIGSEGV 11 无效存储访问时OS的信号。访问了不该访问的内容。
SIGPIPE 13 重要!管道和socket
SIGCHILD 17 重要!子进程终止,父进程收到这个信号就可以回收
没有事先定义好的信号有两个:
SIGUSR1 10
SIGUSR2 12 功能是由用户自己决定的
5.3 进程对信号的处理
signal() 函数
signal(int signum,handler)
处理方法 返回值也是sighandler_t处理方法,出错会返回SIGERR,没出错不变
注意sighandler_t可能找不到,这个时候去自己 typedef,man手册里面有,其实就是int
【用signal函数处理SIGINT信号】
include
int main()
{
ptf(before while1)
while(1)
{
}
ptf(after while1)
ret0;
}
//这个时候ctrl+C会接收SIGINT,默认情况是打断进程的执行,即终止进程。
我们有什么办法通过CTRL+C只打断while1呢?
【用signal函数绑定一个处理方法】。注意在不同linux发行版的 移植性不是很好,可能出问题。
inc
void fun(int sig)
{
printf(处理signal: ? , sig)
}
int main()
{
signal(SIGINT , 函数指针fun)
ptf(before while1)
while(1)
{
}
ptf(after while1)
ret0;
}
这个时候ctrl+C,程序不会终止,而是会打印出一句,处理signal:2!没法杀死他了嘿。kill -9或者关闭终端。
进程收到信号时候会执行fun函数。函数的参数就是signal编号。
这里的sig参数,因为与signal绑定了,他是为了100%保证没有错误。比如说多个信号绑定了这个函数。
类比:独立中断和共享中断。
关于处理方法
signal(SIGINT , SIG_DFL) 设置为默认处理。。。额好吧这一句是完全多余的
signal(SIGINT , SIG_IGN) 设置为忽略处理,ctrlC之后无效果,只好强制结束了
signal(SIGKILL , SIG_IGN) 不会成功的,kill在设计的时候指定了不可忽略。Invalid arguement!
signal有个问题,你要建立一个捕获函数的时候是有移植问题的,可能不兼容。
解决方式是使用sigaction(signum,const传参_结构体指针action,输出型参数_结构体指针action)
可以一次得到设置新的捕获函数和旧的捕获函数,也可以用NULL跳过其中的某一个设置。
6.1 Linux 高级IO
Linux一切皆是文件。
高级IO里面也是读文件和写文件,高级IO要解决一些复杂的问题
非阻塞IO
阻塞IO:放在旁边照顾后面的进程,这是LINUX内核的一个常态。
父进程阻塞,等待回收子进程,收到SIGCHILD就去回收子进程。
常见的 阻塞式:wait pause sleep;
阻塞式的好处:非常有利于系统的性能发挥,他是Linux系统设计的默认方式。让CPU时刻在工作状态下。
为什么要实现非阻塞?
阻塞式在多路io时候会遇到问题
比如我同时读鼠标、键盘。
我希望动了键盘来唤醒一个进程,但是设计成了阻塞式鼠标触发,这样我鼠标不动可能会卡一辈子。
这个过程称为多路IO!
实现非阻塞?方法有二:
O_NONBOCK打开文件 的时候设置
fcntl打开文件之后,对fd进行操作,对其添加一个NONBLOCK模式进去
6.2
程序读键盘 标准输入设备,stdin
程序读鼠标
程序读键盘和鼠标
1键盘
inc
main()
{
ptf "before read" //查看阻塞效果
read(0 , buf , 2);
ptf %s, buf;
}
现象是卡在read那一句。只打印了read之前一句,他读fd 0 ,因为没有按键盘就没有读到东西,这样就进入默认阻塞了
写入abcd还不出,是因为LINXU是行缓冲的,按个回车才行。
按回车:读出的 是 ab
2鼠标
inc
main()
{
int fd = open(鼠标设备文件) //注意:鼠标不是标准输入,/dev/input/下去找 mouse,cat去测试
cat测试的时候,晃一下鼠标就行,会出来好多乱码。
乱码是二进制文件 以 cat字符格式输出造成的。
ptf before
read fd
ptf %s,buf
}
读出来的就是乱码。
3同时读键盘和鼠标
默认:东西没有的时候会被阻塞住,等待被唤醒
现在:鼠标键盘都可以被读到,但是用户会非常不爽,必须要配合,这样的设计时不合乎使用的。
inc
main()
{
//读鼠标,ptf , 为了配合这个程序,读到这里必须动鼠标
memset buf
//读键盘,ptf, 为了配合这个程序,读到这里必须动键盘,顺序还不能反
}
阻塞式IO的困境:我的程序阻塞在了鼠标这里,想去读键盘就毫无反应。后面等待处理的键盘急得要死但是由于被坑了,但是也没有办法。软件没问题,问题就是阻塞式IO的缺陷依然存在。
signal的升级版:sigaction。
6.3 fcntl实现非阻塞
inc
main
{
//读键盘
int flag = -1
= fcntl (0 ,F_GETFL); //先获取原来的 flag,
flag |= O_NONBLOCK //原来的flag添加非阻塞
fcntl(0 , F_SETFL, flag);
那个文件 设置flag 传参flag
//这三步之后,0,也就是stdin就是非阻塞式的了
read
ptf after;
}
运行之后立马出来,打印after,而且什么都没读出来。
读鼠标的话,open的时候直接加O_NONBLOCK就行了,很简单
鼠标+键盘:
before
读出鼠标:空的
读出键盘:空的
after
程序结束
这也不是最终想要的效果
改进:
while(1)
{
memset buf,读鼠标ret = read()
if (ret > 0 )
ptf( %s, buf) //read返回值是读到的个数,读到>0就打印
memset buf,读键盘ret = read()
if(ret >0)
ptf %s , buf
}
准确的说,这样的性能不是特别好。非阻塞式虽然能实现,但是不如多路复用IO和异步IO。涉及到CPU的调度,有些难度后面再说。
6.5 IO多路复用
IO多路复用技术:应对一些阻塞式IO的问题
复习一下非阻塞式的缺陷:那就是必须轮询read+print,CPU不得空闲,是一种非常本质的缺陷。
于是我们引入了select() 和 poll()两个函数,他们是unix的两个派别引起的分歧,其思想一样,外部特征不一样。
IO多路复用原理
外部阻塞,内部非阻塞。
假设有A和B两个部分进行多路复用
循环(1) //这里的循环用了一定的方法降低了CPU 使用资源
{
A阻塞
B阻塞
}
[select的简单使用]
inc
main
{
fd_set myset;
fd = open鼠标
FD_ZERO(&myset) 先把他用宏清零,注意传的是指针
FD_SET( fd , &myset)
FD_SET (0 ,&myset) //把我们的fd添加进select准备复用的列表里,然后就可以select了
struct timeval tm;
tm.tv_sec =10;
tm.tv_usec =10; //超时时间定时10s 0us
ret = select ( fd+1 , &myset , NULL , NULL, &tm);
nfds 超时时间
if(ret < 0)
error
else if ret==0
超时
else //大于0,意味着一路IO到达,在此处理我们列表里的鼠标或键盘
if(FD_ISSET(fd, &myset))
//处理鼠标
memset; read; printf
if(FD_ISSET(0 ,&myset))
//处理键盘
memset; read; printf
}
7.2 线程
Linux中的线程是脱开于进程的,可以说是一种轻量级进程,是参与内核调度的最小单元
一个进程中可以有多个线程
【Eg:使用线程实现同步键鼠】
inc
void *func(void *arg)
{
//辅程序(while1读鼠标)--子线程
}
main
{
pthread_t th = -1;
create_pthread( &th , NULL , func , NULL )
线程属性 函数 给线程的传参
//主程序(while1读键盘)--父线程
fd =open..mouse0
...
while1
}
编译要使用gcc xxx.c -lpthread 使用了链接库,不写的话会报链接错误。