> 文章列表 > 24. linux系统基础

24. linux系统基础

24. linux系统基础

 两个进程间想通讯,必须要通过内核,今天讲的信号其实本质也是讲进程间通讯的意思,那么你为什么可以在shell环境下,可以和一个进程发kill-9啊?
shell是不是相当于一个进程?你自己运行的那个进程是不是也相当于一个进程?这样的话,是不是一个进程给另外一个进程发送信号啊?他本质上也是进程间通讯,也是需要通过内核,

作业,在父子进程间执行,psux的命令啊?
在兄弟进程间,完成这么一个功能,同时增加别的功能
父进程里面是不是需要回收啊?回收子进程,
这个代码你看一下你到底哪里没写出来

 

 

我们完成的功能是psux groupby里面的命令吧?
所以说咱们应该是在创建子进程之前应该创建一个管道啊?这样的话,fork出这两个子进程以后呢?这两个子进程是不是都可以使用这两个管道了啊?
实际上在使用管道的时候呢,是不是就使用了两个文件描述符啊?我们操作管道是不是其实就是操作两个文件描述符的?

在循环里面创建了两个子进程,在36为什么加break?
为了让子进程不再去fork子进程,
循环创建两个子进程,

 

 

 

 

这个wpid>0 这个是不是表示子进程已经死掉了,至少死掉了一个,至少你可以调用这个宏打印一下他的退出状态,65--71

父进程做的事情就是这么些个,这一块咱们前面是不是写过两三次了?

第一个子进程 i==0 79 我们的目的是让他往管道里面写吧?他执行ps aux 这个命令 这一个和接下来的这一块,是不是和咱们前面讲的那个图是一模一样的?只不过是把那个父进程变成子进程吧?
所以说他用哪端就留哪端,不用哪端就关闭哪端,在这他用的是管道的写端,所以他关闭的是管道的读端,

他执行的dup2这个函数 是不是可以实现文件重定向啊?文件重定向和文件描述符的复制,本身上是不是一个意思啊?两个说法而已

那么他要将谁重定向谁?是不是应该是把标准输出重定向到管道的写端啊?所以这一步操作就是干这一个事情的,这一步操作是不是前面咱们说过两三次了?还记的将标准输出重定向到文件当中去吗?那个只不过fd[1]是一个open打开的一个文件啊?在这他是个管道 本质上是不是一样的啊?
在这执行了一个命令 execlp这个命令 86,这个命令内核当中所做的操作是不是其实是将ps这个命令他的代码端,是不是替换掉了,当前这个子进程的代码段啊?如果说这个函数执行成功,后面perror这个函数还能执行吗 87 89?不能执行了,其实 如果成功 后面这个相当于废话,冗余代码 后面可以没有,那是不是不用写了?因为他有可能失败,那你比如说你这个ps你写错了,或者你的参数不对,都有可能会报错,出于习惯你把这个写上 89
万一报错的话,你是不是可以看到啊?如果你不写你就不知道了,
看第二个子进程,i==1 第二个子进程 很显然是不是他要执行管道右边的group bash的命令啊?
所以说在这呢也应该是做重定向操作吧?当然用哪端留哪端,不用哪端关哪端,在这他是不是用读端?所以他关掉了写端,接下来是不是和咱们前面讲过的一模一样了?
然后他也做了一个重定向,后面来执行一个命令,这个execlp一定要会用100 
这个函数其实execlp还可以被这个execl函数代替啊?
如果你代替之后,那么你第一个参数是不是要加路径了?怎么查看这个命令的路径啊 which 
你这个用命令查的话,是不是只能查这个系统命令的这个路径,你能查这个普通用户写的这个应用程序的路径吗?查不了了,

这个代码应该说有一定的综合性,在这里咱们综合用到了 创建管道, fork  循环创建子进程的时候需要你注意的事项,

父进程回收子进程 waitpid waitpid 函数的使用,务必要搞清楚

还有这几个状态的宏65 71 这几个状态的宏你记不住,你就查,man  waitpid 
文件重定向操作,在一个子进程里面执行另一个命令,另一个系统命令,或者是应用程序的函数,execl的函数,
综合应用了这些东西,应该是这个代码应该说这个如果大家掌握了咱们前面讲过的那个父子进程间完成ps aux 这样的一个命令的话呢,那么我想在兄弟之间完成也不难,而且咱们这个wpid是不是前面也学过了?如何让父进程回收子进程?大致也会了,其实就是两段代码一组合就可以了?

看懂代码之后,看看是否符合自己的需求,不符合的话,拿过来大概改一改,就可以用了,一般就这么用的,很少会让你从零开始写,即便是从零开始写,我想的话,应该是会有大概的模板,比如说这个代码和这个代码类似,你照着他写就可以了,一般这么来写,如果说研发一个产品的话,从零开始写,那么周期会很长,

他能够用现实的东西,比如说库函数,现成的类等等,他就用现成的,

共享映射区,他的实现原理,将文件内容映射到共享映射区,那么这样的话,你再操作这块内存是不是就相当于操作这个文件啊?那么我们这个共享映射区其实就是一共有两个类,一个是MAP_SHARED 一个是MAP_PRIVATE那其中MAP_SHARED是不是能够将修改内存的数据反应到文件当中去?但是MAP_PRIVATE就不行了,一般时候用的话,就用MAP_SHARED就可以了,
这个关于mmap函数的用法,咱们就不过多的说了,那么这个函数呢,虽然说参数比较多,但是很多参数都是有固定写法的,那么唯一有那么一两个让你准备的 是不是就那个文件描述符了?还有文件大小,其他的要你准备吗?都是现成的,直接填就可以了,mmap他不但能够完成父子进程间,或者兄弟进程间,也就是有血缘关系的进程间通讯,是不是也能够完成没有任何关系的两个进程间通讯啊?当然你要想让他完成任何两个进程通讯的话,必须有一个文件,
还有一个匿名映射,大家了解一下就可以了,匿名映射是不是只能运用于有血缘关系的进程间通讯啊?因为他没有文件,如果两个毫不相干的进程,他就没有联系的纽带,

那么关于这个fifo 是不是更简单了?fifo是不是你得用一个文件啊?管道文件,这个管道文件是不是咱们操作系统里面的系统文件类型是一样,第一个字母是p 那么这样的话,必须是文件,那么两个进程通过打开一个共同的文件来操作这个管道,
基本上复习的文件就这么多

熟练使用信号捕捉函数 sigaction 重点掌握

熟练使用信号捕捉函数 sigrtal 这个不能跨平台移植,只能在linux上使用,在其他系统中呢,他表现的行为有所区别,
sigaction都是一样的,可移植的
你在linux写这个用这个,那么你拿到unixs上不做任何修改,也可以,但是用sigrtal这个的话就不一定了

熟练掌握使用信号完成子进程的回收  是本章的重点,

前面讲回收,是不是父进程在一个循环里面去循环回收啊?那么循环回收还能做其他的事情吗?是不是做不了啊?这儿就可以了

也就是说有子进程

有子进程退出,他才去回收,没有子进程退出他不管,有点类似于 你在网上买的货,你是不是要在家等着货过来呀,你不可能一会往那跑一趟 一会往那跑一趟去问吧,人家打电话你去拿就可以了 最终是不是异步处理啊?,你并不知道你的货什么时候到,对于这个来说,你并不知道你的子进程什么时候死,

 

信号是信息的载体, 在c语言当中,结构体是不是也是信息的载体?
Linux/UNIX 环境下,古老、经典的通信方式,这个信号已经出了很长时间了,他不是一个新的技术,早就有了,现在依然是主要的通信手段
这个信号仍然是两个进程通信的主要手段之一

 

信号的优先级高,
不管程序执行了什么位置,只要是有信号发生了,优先处理信号,信号处理完以后呢,再回过头来再执行,刚才是从哪中断的从哪执行

信号是软件层面的中断,被称为软中断

程序一开始运行在用户区,运行的时候有的时候需要接触到内核啊?下面是内核区,
进程a要想给进程b发送一个信号,首先他其实进程a是发给了内核,然后由内核再给你转发给进程B  进程a先发给内核,然后内核再转给b 那么内核怎么知道转给b 你发信号的时候你kill 你会写一个 当然第一个肯定要写一个哪个信号吧?要发哪个信号 给哪个进程发哪个信号,是不是至少两个信息啊?一个是进程pid 一个是进程的编号吧,信号的编号?

所以说你指定了进程的pid了,那么这样的话,其实是你先告诉内核的吧?你这个进程a kill的时候,你已经告诉内核了,你要给谁发送信号,那么内核是不是就知道给谁转发了?所以说 他知道转给b 是不是返回来也是一样的?把进程b给进程a是不是也是这样的?

比如说我这个里面,是我们这个方块是我们的代码,执行到这个位置了,突然发生了信号,他是不是优先处理这个信号啊?这样处理信号,在这我们画一个,我们写的处理信号的动作是执行一个函数, 这个函数是我们自己写的,用的是回调函数,这个信号处理函数,  
也就是我们在处理信号的意思
这个信号处理完了,回到哪去呢?从哪中断回到哪。举个例子 比如说你这个信号执行到第十行了,突然产生信号了,那么接下来是不是要处理这个信号呢?处理完这个信号接下来是不是又回到10行?从这个图上我们可以看得出来,信号的优先级 要高于其他动作啊

出现信号要优先处理信号,

处理完信号以后呢,再去执行刚刚你从中断的位置上,再继续往下执行,高于普通操作,信号有三种状态,一个是信号产生,这个信号是怎么产生的,这个信号是不是得产生啊?
你这个程序不close掉他会产生信号吗?

sleep 100现在是不是已经执行起来了?
是不是你查的时候man 1 是不是你查的命令啊? 你不写他从1开始发
man 2 查的是系统调用 或者是系统函数

man 3 查的是c语言的库函数

man 7今天会讲

定时器 alarm,每一个进程都有一个唯一的定时器,这个定时器有点类似于手机上的闹钟,

非法访问内存(段错误)会close掉,
除0,你的进程会立刻死掉

命令产生kill

未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态
一个进程收到信号,默认会去处理它,但是如果这个信号被阻塞了,阻塞的意思是不是暂时不让他处理的意思? 他就相当于屏蔽掉他了,这时候就相当于处于未决状态

这个是后面讲 信号级相关操作的时候会讲这个,

递达:递送并且到达进程。 信号已经处理了 就是递达状态

 信号的处理方式 信号的处理动作
有三个 
直接看手册

man 7 signal 这个里面就能够把这个信号相关的信息传达出来,比如说signa1 dispositions 信号的处理方式,term是不是终止的意思?终止进程

Ign 忽略信号

Core内存非法访问照成的

stop进程暂停

Cont 使进程继续执行

stop 和Cont 他俩是一对, 暂厅停止是一对

信号的四要素,每一个信号 用四种属性去描述他
一个是信号的名字SIGHUP  大写的宏叫信号的名字,
SIGINT 是不是刚刚说过了,ctrl+c 是不是可以产生这个信号啊?

value指的是信号的编号

action 信号的默认处理动作,什么叫默认处理动作啊? 那么对象如果你的进程不处理他,他有一个默认处理动作

后面咱们会讲怎么捕获他,
comment 这个信号怎么产生的,一个信号描述,
被键盘中断,其实就是说的ctrl+c

SIGPIPE 往一个没有读端的管道写数据,你把他的管道读端给关掉,然后你可以写数据,你看看会不会收到这个信号,一定会收到这个信号,

SIGCHLD重点讲的这个信号,Ign 忽略,什么情况下才会产生这个信号?是不是子进程 停止或者终止

停止相当于暂停的意思,stopped 和terminated是不一样的,terminated是终止的意思,进程彻底死掉了

The signals SIGKILL
and SIGSTOP cannot
caught,b1ocked, or ignored
不能阻塞 不能忽略,

收到这两个信号SIGKILL  SIGSTOP 必须有处理动作,这两个处理动作一定是默认动作,不能定义,只能是默认动作吧?

SIGKILL的默认动作是什么呢?Kill signal默认是终止的意思,Term

SIGSTOP stop 是暂停的意思

在这里面信号里面有3种值,

这个信号的编号,有的是有三种值,SIGUSR1 SIGUSR2 
在一个系统上只有1个值,在linux系统下他是终端这个值,其中你不知道这个也没有关系,因为你在使用信号的时候呢,你不要使用编号,你使用宏名就可以了,那么宏名你管他是什么,不同的系统上他表现的数值不一样,对于这个宏是不是都一样的?

别的信号用的相对比较少,SIGIOT SIGEMT
SIGSTKFLT ... 你也不用再关注了 很少用它

其实信号的处理动作还有两个,是暂停和继续执行,大部分都是终止,终止是不是才会对你的系统照成的影响最小啊? 你比如说你这个你本来进程被core掉了,你如果不知道怎么终止的话,他是不是会非法访问其他内存啊?有可能给你修改吧?这一修改不得了,其他程序也会core掉了,是不是会照成连锁反应啊?

忽略 丢弃不处理的意思,相当于就当他没发生

捕捉信号 捕捉是咱们需要注册shell命令函数,让他去执行我们自己定义的函数,

信号有很强的延时性,信号处理是不是要进入 内核啊,我们信号是不是需要暂时的阻塞啊?那么阻塞 如果说后面呢你不阻塞,再写出注册,这个信号是能够被处理的,从这点来说,我们可以知道,这个信号是有延时的,我们介绍信号集相关函数,我给你介绍阻塞这一块你就知道了,一开始你不处理他,后来呢,你又想处理他了,然后解除注册,这个信号就能够被处理了,从这一点来说也能够感觉到这个信号是有延时性的

然后你去不阻塞他的话呢,他的这个延时你是感觉不到的,因为这个时间十分的短暂,这个信号处理的话呢,他会进入到内核,
咱们前面讲到pcb的时候给大家说过了,这个信号集相关的东西是不是都在这个pcb里面,在这里面主要指的是阻塞信号集和未决信号集。

 

信号相关的函数, 先给大家讲一个,

signal函数 咱们用的比较多的,
完成一个信号的注册,注册一个用户自定义的函数 言外之意,这个信号是要发生的,就会去调用信号处理函数,
是不是和

这个图这个意思啊,这个代码执行到这个位置之后产生的信号,是不是就会执行信号处理函数啊?

这个函数是用户自己写的,用户自己写的函数,还得调用signal函数完成注册,不然的话,人家怎么知道执行哪个函数啊?

 typedef void (*sighandler t)(int);
sighandler_t signal(int signum,sighandler_t handler);

signum信号的编号,每一个信号是不是都有一个编号啊?数字就是编号,但是你使用的时候用哪个啊?用宏 不要用数字,sighandler_t  第二个参数是信号处理函数的函数名 函数名代表函数的首地址,就是指针嘛 函数指针,那么这个函数指针,
指向这个函数呢,有一个参数 int类型的,那么你说这个int类型是个什么东西?
信号的编号,

前面给大家讲pipe 讲管道的时候是不是给你说
如果说读端全部关闭了,然后你再写管道的话是不是会发生一个,会产生信号啊?

创建了一个管道pipe 我关闭了写端,

 

后面是读吧?我把读端给关闭 让我写,
0是读端,我把读端关闭了
调用signal注册一个信号处理函数,signal在哪调用都行,在关闭读端前面调用也行,

第一个是信号编号,第二个是信号处理函数,我主要是想让大家看一下,到底我把读端关闭以后,然后我再写入管道是不是会产生一个信号,
接下来是不是你要写这个sighandler函数了?
一个void 类型的

 他有一个参数 signo,那么咱们打印一下 这个signo到底是不是这个sigpipe信号,

 sigpipe 是13 打印这个signo是不是13就知道了吧?
产生了13信号,这个信号是谁产生的?内核产生的信号发给当前进程 我把这个代码原理再说一下,
代码很简单,调用pipe创建一个管道,创建管道,是不是在内核就有一个缓冲区了?读写两端来标识这个缓冲区,我们调用signal函数,完成SIGPIPE信号的注册,第一个参数是信号的编号,的个参数是信号的处理函数, 也就是说这个信号发生之后 去处理哪个函数的意思 去执行哪个函数,举个例子,假如说我这个函数执行到 write这个位置了,他一写 你如果写完了 这个时候是不是信号产生了?
因为他是不是正在往一个没有读端的管道写数据啊?内核检测到这个错误了,立刻就产生了一个信号,那么这个信号呢 产生信号是SIGPIPE信号 而且 我们这个当前进程是不是也注册了这么一个信号啊?内核就知道这个信号产生之后,去执行这个sighandler函数,sighandler函数是内核执行的还是你用户进程执行的?是内核执行的,其实这个回调函数,你想一下 回调函数一般是让你自己写这个函数,让别人去执行,

回调函数,实现是用户进行实现的,但是执行,不是用户进行执行的,对于这个sighandler函数,是内核执行的这个函数,那么内核是怎么知道执行这个函数了?因为你已经告诉他了28行,那么这个其实这个注册呢 如果严格的说是这样说
给内核注册一个信号处理函数,是给内核注册的,那么内核检测到这个信号产生了是不是立刻就会执行这个函数了? 它这个函数打印出来这个signo 13
这个是不是验证了咱们前面的说法吧?

给一个没有读端的管道写数据,会产生signo信号,是不是这样的?

信号的特点是,必须在某种特定条件下才会产生,

信号的产生,信号比如说 按键盘,命令 函数 软中断 硬件中断 是不是都会产生信号啊?

信号的状态 信号有3种状态,必达产生状态 未结产生状态 产生状态

信号的四要素
每个信号多有编号,
信号的名字,那个宏就是名字,我们用名字,不要用编号

信号的默认处理动作
大部分的信号的默认处理动作是终止 

因为只有这个进程终止之后才会对系统产生的  (  )最小,
信号是如何产生的,是不是后面两个comment啊

action是不是默认冲突  也就是说这个comment是描述的这个信号是如何产生的,是不是前面咱们前面讲的这个信号扩展的时候说过了,信号是在某种特定条件才会产生吧,这个信号虽然说有这么多个,但是对于我们来说 有些信号需要我们了解一下,但是 这些信号 几个常用到的信号 你大致了解了解,因为这些东西用的相对较多,你得知道,比如说SIGINT 2号编号吧?是不是ctrl+c产生的?

SIGQUIT ctrl+z+/ 使进程退出

SIGKtLL和 SIGSTOP.是不能被捕获 阻塞忽略的,
SIGSEGV 这个信号呢,是非法访问内存会产生的信号
端溢出,内存溢出等等都会产生信号

SIGUSR1、SIGUSR2 这两个信号,是系统留给咱们自己来使用的,是用户信号,这两个信号 我们自己可以来用,其他信号你不要乱发,因为它都是在某种特定条件下产生的,但是你不能乱用,但是这两个信号 SIGUSR1、SIGUSR2  你自己随便用

SIGALRM后面会讲,会产生的信号,
SIGTERM使进程终止,
SIGTERM和SIGKILL有什么区别?都是使进程终止,SIGKILL这个信号不能被捕获,
SIGTERM能够被捕获但是,这个信号的默认处理动作是终止,那么如果说你捕获之后,你可以让他不终止,

SIGCHLD重要的一个信号,这个信号是子进程退出之后,内核会给它的父进程发出一个信号,那么他的父进程收到之后,回收子进程
这就是父进程回收子进程的一个时机

SIGSTOP使进程暂停,
SIGCONT使进程继续执行,

 

信号默认处理动作,信号默认处理是5种,

信号注册,注册由,信号产生之后,会执行信号的注册函数,那个是一个回调函数,那个函数实现是用户实现的,调用是内核调用的,
后面讲信号的处理过程的时候,再说

 

 

kill函数,他对应的系统命令是kill命令,
给指定进程发送指定信号,那么指定进程是怎么标识?是不是进程的pid啊?发送指定信号,那么这个信号怎么标识啊?是不是信号编号啊?
所以说你使用这个函数的时候,肯定是要有两个参数,第一个产生是编号,第二个参数是进程

kill命令在使用的时候是不是也是这样kill -9
9是信号编号,后面来个pid pid就是指的是进程的pid 这就标识了,给哪个进程发送哪个信号

函数原型,int kill(pid_t pid, int sig)
pid_t这个变量的类型见没见过?是不是进程pid啊?进程pid就是这个类型, 第二个是signo 信号的编号

成功返回0  失败返回-1,

pid > 0:发送信号给指定的进程。

pid =0: 发送信号给与调用 kill 函数进程属于同一进程组的所有进程。

举例:父进程循环创建3个子进程,这三个子进程和父进程是不是同一组?如果说你要想把这四个进程一次性全部给杀死, 函数的pid 参数写0,就可以了,那么这个0是什么意思呢?你要给这个当前进程,他自己,调用这个kill函数进程 是不是叫单独进程啊?给他的一个组发,发哪个信号都可以  发个-9就可以了

这样的话一删就可以把这4个进程全部给他杀死,是不是相当于杀了一个组啊?

pid < -1:取|pid|发给对应进程组。
如果pid< -1表示 取pid的绝对值发送给对应进程组,比如说你这个进程组,他的pid 他的组id是100,那么你这用的时候用多少?写-100就可以了
那么写-100表示给这个组发呢,这个东西杀伤面都比较大,一般不用

pid = -1=发送给进程有权限发送的系统中所有进程。
这个杀伤面更大,
如果你在root用户里面调用这么一个函数,

是不是其他的所有的用户全部都受影响啊?因为root用户有最高权限,root用户可以给普通用户发送告急信号,反过来,普通用户能不能给其他用户 或者root用户发送啊? 没有权限,

这四个里面虽然说了4个,但是绝大多数情况下,99.9%的情况你只用第一个,相对安全,假如说 后面的这些参数你都不知道,你只知道>0的这么一个参数,那么你能不能把这四个进程全部杀死?来个循环就可以了

每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组 ID 与进程组长 ID 相同。

比如说多进程服务,一般情况下都是共同协作 完成一个特定任务,每一个进程组都有一个组长,默认进程组 ID 与进程组长 ID 相同

一个父进程创建三个子进程,很显然肯定是父进程是组长

写一个简单的程序
kill函数测试
kill 也有头文件

kill自杀,

 打印这句话,看看能不能打印出来,如果说没打印出来,说明这个进程已经死掉了,

父进程能不能杀死子进程啊?
可以
子进程能不能杀死父进程?

把进程组的概念也说一下

 

 严格的说不是父进程杀死子进程,而是说父进程给子进程发送信号,kill这个函数其实不是杀死的意思,在这是给谁谁谁发送信号的意思 或者子进程杀死父进程

这个是循环创建三个子进程,

我先把这个进程组的概念先给你提一句

为什么shellp(10) 让他不退出,便于我们观察

 

 

ps -ajx

第一个是父进程,pid 这个是 
pid 是哪个进程的
pgid是组id
sid是会话id

2016是这个kill1进程的父进程 他的父进程应该是-bash啊?

2369是他自己的pid
组的话也是2369他自己

他的会话id 2106 是不是bash啊?

2369 接下来这三个是不是都是子进程啊?这三个子进程很显然 父进程都是2306 子进程自己的pid 各自都不一样  组都是一样的

2369 和pid 2369是一样的 也就是说 这三个子进程 和这一个父进程他们的组id就是父进程的pid
会话id 也是同一会话,2106 是不是bash啊?
我们这四个进程 当然是包括一个父进程 3个子进程 在一个组当中,也在一个会话当中,实际上一个会话会可以包含一个或多个组

在这我们只是看到一个组,通过这个例子我们就看出来了,就是说父进程循环创建3个子进程,这三个子进程和一个父进程 他们在一个组,而且组id 就是父进程的pid

先让子进程 杀死父进程 如果说你在36写 会发生什么情况,如果说我在这kill 严格的说不是杀死父进程,是给父进程发送信号,这个一杀,父进程马上死掉,你说这个 咱们说拥有4个进程?如果说父进程死了,你说这个组还在吗?只要这个组有一个进程存在,那么这个组就不会消失,
 

我这样写会有什么情况发生呢?我这个父进程 刚刚把这一个子进程创建出来以后,后面这两个是不是就没有了,44-54这个咱不管,只要能杀死一个就看了,咱们测试的目的是让子进程杀死父进程啊?已杀死,咱们看一下这个是不是已经创建完了,看来是父进程,他是把时间分析完成之后呢,这个子进程才获得的cpu
子进程是可以杀死父进程的,

反过来,父进程杀死子进程

 放到58 但是你这样你不知道pid 了,所以你只能放到上面,你创建的时候 是不是你知道啊?
发一个if(i=0)就杀第一个,这个给大家验证个什么?就是父子进程间都可以相互杀的
父进程杀死第一个子进程

怎么一个都没杀死啊?他可能跟本就没跑起来就死掉了,还没来得及执行立刻就死掉了,这个大家知道是什么意思就可以了,父子进程之间都可以相互杀死,

进程a 进程b 两个进程之间不是父子关系,这个a知道进程b的pid 他能给他发吗?前提是他们俩是必须处在一个用户下,如果处于两个用户下可以吗?没有权限,
a用户能给b用户发信号吗?发不了吧?
a用户下的进程 不能给b用户下的进程发信号,
 

pid = 0:
发送信号给与调用 kill 函数进程属于同一进程组的所有进程
试一个

 先注掉,当i=0的时候,sleep(1)之后再杀,杀死同一组的所有进程,三个子进程 和一个父进程全部死掉,
pid=-1 的话权限太大了,他这一杀,搞不好连这个bash窗口也给你杀死了,

 

 ps -ef 是不是全杀死了,你看一下有没有子进程活着就知道了,那三个子进程是不是奖会sleep(10)啊?全部死掉了,

试试-1,看看bash这个玩意儿会不会死掉,死掉了,这个是非常危险的,你看有很多人连你的 服务器,你也不知道谁连的 怎么办?直接开一个这玩意儿全部干掉了,或者你这么干,比如说你看到这个bash,比如说你自己就开了一个,是不是只能有一个bash,然后实际上你看到好几个,你那个kill,找到这你只能有一个,找到这个之后,你把他杀死就可以了 他就连不上了

 

 raise函数是给当前进程发送指定信号,自己给自己发,他只能给自己发
你看他的参数就可以了,int raise(int sig) 他没有指定的pid
其实是相当于这个
raise(signo) == kill(getpid(), signo);

谁调用的这个函数,那么当前进程就指的谁,那么其实就是指的他自己啊?

死掉了,14这句话是不是没打出来?
另外一个是abort
给自己发送异常终止信号 6)SIGABRT 不会调用失败,并产生core文件,只能成功,
相当于 kill(getpid(), signo);这个函数会产生6)SIGABRT是6号信号,这个函数会产生6号信号,看一下这个函数怎么用,

 

 

 没有产生,abort 会产生core文件,为什么没有产生core文件呢?因为core文件大小为0,
ulimit-a 看core文件
可以改,ulimit -c unlimited无限制大小,有core文件了,

gdb core 给大家调一下,

这个是不是得加-j啊?这个不给你试了,这个core文件一般产生比较大,

这样的函数abort用处不是太多 简单了解一下

时钟定时器, 有点类似于手机上的时钟,那个是不是设置闹钟的?这个也是,

unsigned int alarm(unsigned int seconds);

unsigned 返回一个无符号整型值,

seconds很显然这是一个描述,这个描述一定是一个大于0 数,设置定时器或闹钟,在指定

seconds 后,内核会给当前进程发送 14 SIGALRM信号

进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个 定时器

是不是终止呢?

默认处理动作是term 终止吧?然后触发Timer signal from
alarm(2)是不是这个函数产生的啊?2是什么意思啊?这个2 是你man的时候 你man 2 alarm

这写3呢?看手册的时候,(2)(3) 要知道什么意思

需要你知道的这一点,每一个进程 有且只有一个唯一的一个 定时器

如果说我弄两个呢?弄两个 最后一个有效,前面的覆盖了,

函数返回值 返回0,或者是剩余的秒数, 如果你是第一次调用,返回0,成功返回0,失败返回一个非0的值,第二次再调用,返回上一次你刚刚定义的那个秒数,

man 2 alarm 没有返回负值的情况,要么返回0,要么返回剩余的秒数

看这个函数应该是不会调用失败,如果一开始的话,你是不是写了一个alarm(5)啊?是不是相当于5秒钟之后会产生一个信号啊?

 

是一个alarm信号, 5秒钟之后会产生一个信号,接下来你sleep(2)休息2秒钟,再调用alarm(5) 返回的是上一个时钟剩余的秒数,上一个时钟是不是已经过了2秒了? 返回3,接下来,是不是再过5秒钟才会产生信号啊?是不是这个最后一个alarm5 把上一个alarm5给覆盖了?那么一个进程只有一个闹钟,只有一个时钟,那么第二个alarm会把第一个alarm覆盖,

还有一个是alarm0如果说你这个时候你不需要了,你写一个alarm0就可以了,alarm0表示取消闹钟的意思,alarm这个时间,括号里面只有秒数啊,那么这个秒数怎么计算的呢?他指的是自然定是法,那么什么叫自然定时法呢?

进程在执行的时候,他是不是有挂起啊,就绪啊,阻塞啊,是不是有很多这样的状态?这个时间跟状态无关,只要你这个alarm只要是写上去了,这个时间开始计算了,到5秒钟的时候呢,他就开始发送信号,你说你这个进程比如说从第一秒启动,比如说在第二秒,第三秒执行完了,你说你的进程是真正的执行了两秒钟吗?不是3-1等于2吧?这个两秒钟期间你这个进程是不是要切换了好几次,因为你这个进程肯定就绪态,进行态,运行态,挂起 等等这些状态,那么这个alarm他不看这个状态的,他看的是自然定时法,这个时间只要过去了就过去了,因为那些状态,你也没有办法计算吧?你知道你的这个程序用了几个cpu时间片吗?
你知道就绪态等待多长时间吗? 挂起态等待多少时间吗?是不是不知道啊?
所以说他的时间是自然定时法,
 

 写个例子测试一下,

我一开始想要设置一个闹钟,接下来打印13这句话,sleep(2)以后,你再alarm(1)然后看他的返回值时几
然后我要让你知道这个alarm发送的是不是一个alarm信号 SIGALRM,

怎么验证呀?是不是咱们得注册信号处理函数了?信号产生之后呢,咱们打开的是不是是一个alarm信号是不是就知道了?
得注册一下啊?注册是不是在前面注册啊?
在这我有一个问题想问一下大家,看他打印的值是不是SIGALRM信号就知道了,是不是6啊?

 就这个信号这一块,有点类似于qt里面的信号槽,根据信号是不是有点类似啊?朝着那个方向理解也可以,那么再问一下大家,现在是不是我注册了一个信号处理函数了, 当这个信号产生之后,那么这个函数就会执行,内核就会去调用,你说我注册了这个函数以后,这个函数刚开始注册的时候会执行吗?这个是一个误区,有的人认为我这写了这一个函数之后16,注册完之后,这个函数就会被调用9-12
这是错误的,不会执行,只有当信号产生之后,9-12这个函数才会被调用,在这是不是写了一个alarm(5)是不是设置一个时钟啊?这个函数调用完之后19,你这个9-12会执行吗?也不会,也是注册一个,是不是给进程设置一个闹钟啊? 但是5秒钟之后,如果说没有22-24的话,这个程序就会发送收到一个信号,SIGALRM 你这样写,运行之后进程结束了,应该让程序再休息一会10秒钟26
如果你不加,这个函数程序是不是瞬间执行完了
瞬间执行完了,时钟还有用吗?进程结束 所有东西全部销毁啊?

一个观察返回值,按理说这应该是几?第一个是0,第二个是3吧?

第二个产生信号你看他是不是6号信号就知道了

 执行alarm0到底能不能把alarm取消呢?

 你说我打印这个28应该是几啊?应该是2,你注册的是2,因为这个时钟已经取消了,接下来要sleep(10)

练习题 2: 编写程序,I测试你的电脑 1秒种能数多个数字

用alarm(1)然后你在你循环里面打印多少个数,1秒钟过后会产生SIGALRM信号,这个信号产生之后默认会终止进程,printf 遇到\\n才会打印出来,或者是缓存区满了,如果你这样数的话,是不是死不了了?还用写处理函数吗?alarm(1) 1秒钟过后会发出一个SIGALRM信号,这个信号默认处理动作是不是终止啊?终止了是不是进程退出了?这样的话是不是就可以1秒钟我们打印多少数了?

肯定一开始不会产生sp,只有说,他进入这个循环里面,打印1秒钟过后 产生信号了,这个就终止了,

 real 指的是总共的时间 执行了1.2秒 

user 用户区的时间

sys 内核区的时间

他会涉及到用户区到内核区的切换,那么我这个printf 会不会涉及到用户的切换啊?

printf内部会调用write write 函数会调用sys_write 这个涉及到用户区到内核区的切换

实际执行时间=系统时间 + 用户时间 损耗时间

 

我怎么让我这个值打印的多一点呢? 我把这个回车去掉行不行?去掉应该打印的多一点吧?你这样打的话是不是相当于每一个数都打啊?每一个数是不是都会泄露一个用户区到内核区的切换啊?你把这个\\n去掉呢?这样切换的次数是不是大致减少了?

 我重定向一个文件当中去,你说这个 这个是不是相当于写文件操作了?那么写文件操作是不是相当于带缓冲呐?一个带缓冲 一个不带缓冲 哪个效率高?带缓冲的效率高 如果说你在写代码的时候,如果说你在写文件操作,fopen fclose fread这样的函数 ,你不要用openread write一个带缓冲,一个不带缓冲

 从这个时间来看的话,实际执行时间接近1秒,而且用户区+ 内核区一共0.948 很显然这个损耗是非常小,

文件重定向效率高很多,为什么效率高?

为什么效率高?为什么24这么执行效率比较低?原因是:

所以涉及到用户区到内核区的切换次数大大减少

一个是文件重定向,一个是直接打印,那么

他们的效率差别很大,差在哪 主要就是由于从用户态到内核区的切换的时候呢,损耗的时间,那么这个时间是不是很浪费时间啊?

 

setitimer比alarm函数的优势在哪呢?alarm函数是不是只能调用一次alarm函数是不是只能产生一个alarm信号啊?

一个alarm函数只能产生一个,alarm函数那个参数指的是时间吧?到那个时间点他只能发送一个信号,但是我们有这样的一个需求,我们想周期性的触发,不只是让他产生一次,这个时候你用alarm不太好使了,你是不是可以在whele循环里面不停的让他触发也行啊?相对来说比较麻烦一些,相对更好的实现方法叫setitimer

这个函数返回值 成功返回0,失败返回-1,

参数意思 可以周期性的产生信号,

which指定定时的方式,到了那个时间点以后呢,他产生的信号是SIGALRM信号,这个和alarm一样,alarm是不是用的也是自然定时法,那么这个自然时间里面是不是这个进程的状态不管什么他都计时啊?不管是sleep也好,就绪,运行 时间都算,

虚拟空间计时(用户空间):ITIMER VIRTUAL> 26)他产生的信号是SIGVTALRM,这个一般情况下我们用的不多,这个计算 他只是计算到用户区的时间,我们这个进程执行的话是不是有时候在用户态 有的时候在内核态?

后面

运行时计时(用户+内核):行系统调用的时间 这个包含了损耗的时间了,包含了用户的休眠 挂起啊等等,这个时间你能计算的准吗?

所以说这三个里面,虽然说他介绍了3个,一般情况下用自然定时,所以说你把这个掌握了就可以了

后面 

第二个参数是const structitimerval *new_value  是一个结构体,你看到参数的时候 如果你看到前面有一个const表明他一定是一个输入参数,那么这个结构体,稍微麻烦一些

structitimerval 这个结构体有两个成员,而且每一个成员是不是也是结构体啊?

 这个是不是相当于结构体嵌套啊?这个类似于这个,假如说这个大结构体是a

a内有个结构体叫b  b 里面有个成员叫c 那么你怎么引用这个c啊? 这个c怎么用啊?

是不是a.b.c 如果a是一个指针呢?是不是a->b.c啊

 struct timeval 只有这两个成员一个是,long tv_sec;秒
long  tv_usec;毫秒

这个意思是精准度很高,你这个alarm只能精确到秒,

我举个例子,假如说我想设置3.5秒 这个时间怎么设啊?tv_sec =3

tv_usec=0.5 当然你写的话,不能写0.5秒  1秒1000微秒 

tv_usec=500就可以了

这个时间是不是两个加在一起啊?

old_value 存放旧的 timeout 值,一般指定为 NULL 如果说你在一个函数声明里面看old 什么的,他是一个传出参数,他存放的是一个先前的一个设置,原来的一个设置,如果说你第一次用这个的话你会有原来的设置吗?第二次再次调用是不是有了?

一般我们这里设置为null就可以了

这样的话,需要传参的话,这个which也需要传参啊?当然这个which 传ITIMER REAL>

对这个*new_value的话你怎么赋值啊?你比如说你第一变量用tm 是tm.it interval.tv_sec;赋值

然后再. tv_usec;再赋值

这个struct itimerval 结构体有两个成员,

这两个一共有两个结构体,一个是it_interval;闹钟的触发周期 一个是 it _value;闹钟的触发时间

这两个是什么关系呢?,

举个例子,我们想让我们的进程呢,什么时候产生闹钟 时钟呢?3秒钟过后 每隔1秒钟产生,

这个3应该在it_value赋值,具体赋值在

long tvIsec;=3

long tv_usec;=0

每隔1秒钟,接下来it interval 每隔1秒钟 意思是周期性的意思,

it_ interval.tvIsec=1 .tv_usec=0

大家要对这个结构体的用法要熟悉起来,因为咱们后面再讲这个socket atmi原型的时候像这样的用法还有很多,

像这种类似的用法,结构体套结构体

为什么用结构体套结构体?一个是结构的性质,再一个是可以能够表示更多的信息,那么你使用结构体的话是不是就可以表示更多的信息啊?

如果说你不使用结构体,就让你传什么啊?传整型值,是不是也可以? 你是不是得传4个啊?

相对来说麻烦一些

周期性

功能设置时钟 能够周期性的触发时钟 

每个参数看手册 看文档, 

ITIMER REAL 是不是有没有过期的意思,发送信号递送传达信号

三秒钟过后每隔一秒触发一次,结构体是两个成员

定义一个 itimerval 周期赋值的意思,有两个成员 赋值两次,

tV_usec:你不写的话他是不是有可能是随机值啊?随机值有可能会很大,有可能会很小啊?

我们这个变量是不是在栈上?这个你不赋值的话,他就是一个随机值,

这个是什么时候触发,第一次触发的时间吧?

3秒钟过后,每隔1秒钟 就会产生 一个SIGVTALRM信号,

接下来你才去调用这个setitimer 前面的那些都是赋值啊,这个是不是相当于注册一个时钟啊?或者安装一个时钟,只不过这个时钟是一个周期性的,alarm是不是就触发一次啊?

第一个要用自然定时法,第二个是指针,需要把这个tm地址变量传给他啊?第三个不关心传null

你如果仅仅这样写的话,你能够检测到这个他到底有没有产生SIGVTALRM信号吗?因为这个SIGVTALRM信号默认是终止,

那你这样写,肯定不行是不是还没有触发就退出了?所以第一个是我们是保证不让他退出吧?

所以sleep(1)这样我的这个进程是不是不退出啊?

 

我们看一下这个setitimer函数,他到底发送的是不是SIGVTALRM信号吧?我们看一下这个信号的注册啊?

 这个函数signal调用完以后,不是说立刻就产生信号,这个只是一个注册,只有说这个信号产生之后,他注册的信号处理函数才会被执行,打印的是14编号

 那么也就是说咱们这个程序 一开始先过3秒,是不是没什么动作啊?然后过3秒之后,每隔1秒钟13这句话,会打印出来,这个是一个周期性触发,

终止进程,这个 代码里面我们先注册一个信号处理函数,我们注册的是SIGALRM 信号,  信号处理函数是sighandIer 接下来我们设置一个周期性的时钟,这个结构体怎么赋值,你一定要学会,这个结构体是不是结构体里面套结构体啊?那么你用的时候要这么用tm.it_interval.tv_usec =0;

相当于a.b.c

大的结构体是在最外面, 小的结构体在里面,第一次触发的时间在3秒钟,过3秒钟开始每隔1秒钟

实际上 第一次时钟产生是第几秒啊?应该是第4秒了,到3秒了,每隔一秒钟,第四秒产生一个,然后第5秒钟1个,第6秒钟1个,每隔一秒钟产生一个,比如说第一次产生是1秒钟,第二秒不产生第三秒又来一个啊?每隔一秒钟

已经捕获了,捕获的意思是按照你的方式来啊?

14号信号是term 默认是中 当然你要捕获,他还会执行默认动作吗?你捕获他就不再执行默认动作了 我按ctrl+c  3秒钟过后,每隔一秒钟产生一个信号,这个打印这句话,是不是每隔1秒钟打印一次这句话?回到while(1)sleep(1)那里去了?这个时候我按ctrl+c 是不是终止啊?ctrl+c 发送哪个信号啊?SIGINT 2号信号 2号信号默认处理动作是什么?终止进程,因为我们没有对SIGINT进行捕获,如果你捕获了,你可以让他不终止,也就是说你这个信号处理完以后 12  13  14,你这个信号是不是已经执行完了?

执行完以后他应该是回到了while 35 -39循环里面吧?1秒钟过后又立刻产生这么一个信号了11-14? 产生是不是再次调用啊?再完成以后再回到36-38这里面来

如果你没有这个sleep 1 你这个进程是不是马上就退出了?

你退出之后还有什么时钟啊?

 

信号集有两个重要的概念,一个叫阻塞信号集 一个叫未决信号集 信号相关的内容,都保存到内核当中的pcb当中啊?那么大的结构体,其实你查看这个tas strus的时候应该能看到sigset_t 这个信号的变量,那个就是信号集,信号相关的东西,就在内核当中,什么是未决信号集 什么是阻塞信号集  未决信号集里面保存的都是没有被处理的信号,咱们前面在讲基本概念的时候是不是给你说了?信号的几个状态,产生信号是怎么产生的,未决的意思是不是就是这个信号没有被处理的?

没有被处理的信号叫未决信号集,还有一个叫地达 地达的意思是这个信号已经被处理了,这个未决信号集的意思应该就是这个集合里面保存的都是没有被处理的信号吧?阻塞信号集的意思是我们的这个进程 有时候需要让一些信号先不处理,这个时候你可以把这个信号给他阻塞,阻塞 很多信号,比如说有些信号需要阻塞,那么可以把这些信号保留在阻塞信号集里面去,那这个就是阻塞信号集的意思,是不是当前进程需要被当前进程阻塞的信号啊?的一个集合就叫阻塞信号集,这两个信号到底是什么关系?在这我重点给你说明这一个

这两个信号是什么关系?

我以一个信号为例,2号信号 ctrl+c 以这个为例来解释一下 这两个信号到底是什么关系,我们这一个进程启动起来以后,一个程序一执行,是不是相当于进程启动起来了?就成了一个进程,那么我们这个进程有的时候能够收到一些信号,比如说2号信号,那么一个,我们进程收到这个2号信号以后呢,那么他首先就留在了这个未决信号集当中,

比如说2号信号,一开始的时候呢,他先留在未决信号集当中,此时他的值为1,在我们信号集当中,这个信号集他的变量叫sigset_t 你这种就是一个数据类型,你可以把这想象成int  anset  sort  这就是一个数据类型,只不过这个类型是不是定义的用tyf 定义的?

 这个具体是多少呢?我在这里给你列出来了

 unsigned long int 是不是这个就是他的数据类型啊?你不要把他想的太复杂,你就认为他是一个信号集 就是一个变量类型就行了,不要想得太复杂 你就把他想象成 int 或者longint 都行,

sigset t 这个类型刚刚看了,他是一个 unsigned long 

这个类型,我们用的时候怎么用呢?他是用了这个里面的位操作,只要涉及到位 操作,那么他只有两种值,0或1,0表示没有 1 表示有 在我们这呢 1 2  3 一直往后排,这里面1保留的是1号信号, 2 保留的是2号信号, 3保留的是3号信号,那么没有编号为0的信号吧?这个信号一定是从0开始的,没有编号为0的信号,

刚刚我们说了 2号信号现在,我们这个进程是不是收到了 2号信号啊?这个时候呢?先保留在未决信号集当中,此时值为1,是不是表示这个信号还没有被处理呢?

当这个信号需要被处理之前,他先到阻塞信号集里面去查询了一下,这个信号有没有被阻塞?他要看他对应的位置上 那么这个是2号编号吧?在这呢,他也查看2号编号,假如说这个编号为0,表示这个信号没有被阻塞,这个编号为0,表示阻塞信号集当中没有这个2号编号 的信号吧?

也就是说这个信号没有被阻塞, 那么这个信号他就能够被处理, 也就是说不被阻塞,当这个信号

2号信号他被处理完以后,这个结果,这块2号这个的标识位由1变成0

也就是说你这个信号到底需不需要被处理,有一个先决条件 要看他是不是被阻塞,如果被阻塞了,那么 他就不处理,如果没阻塞,是不是这个信号,他处理完以后 那么这个2号编号对应的标识位上由1变成0标识该信号已经地达了,地达是不是被处理的意思,反过来 假如说在查看阻塞信号集的时候呢 这个位不是0,而是1 1表示这个信号被阻塞,那么这个未决信号集里面2号编号,对应的这个位上的1 仍然保留,表示这个信号留在了未决信号集当中,也就是说实际上这个1是不是没变呐?仍然是1,这就表示被阻塞了,

 

 

 

 但是注意一点,假如说后续操作 我们把这个阻塞信号集当中这个2号编号对应的位上这个阻塞信号集当中这个2号信号给他解除阻塞 是不是有阻塞 就有解除阻塞啊?

解除阻塞 也就是说这的值 这个1变成0了,他这个以后呢,是不是表示这个信号显示解除阻塞了?

 那就意味着 未决信号集里面的这个信号,仍然需要被处理 他处理完以后呢 这个信号2号编号对应的标识位他仍然由1 变成了0 表示这个信号已经被处理了

通过刚刚描述的这段话来说,这个未决信号集里面, 这个信号是不是说只是暂时不会处理啊?那么他到底能不能处理 就是该不该处理 取决于阻塞信号集当中这个对应的标识位上,他是不是被阻塞了,如果没被阻塞那就意味着需要处理,如果阻塞了那就需要什么?这个未决信号集这个位是不是保持为1啊? 后续如果说呢 这个进程当中呢,这个阻塞信号集当中这个2号编号,这个位上由1变成0了,那么就意味着什么?这个信号是不是解除阻塞了?未决信号集里面对应的这个位上是不是 因为他一开始是不是未决啊?是不是后续还需要被处理啊?处理完以后是不是由1变成0了?