/sbin/init 进程之父。进程号总为1。
环境列表。每个进程都有,其中每个元素都由一个名称和对应值组成。
shell中创建环境变量export myvar = 'hello world'
。比如shell自身就定义了一些环境变量: HOME, PATH等。
信号-软中断。 信号在产生至送达进程期间一直处于挂起状态。当对应进程获得调度时,处于挂起状态的信号会被同时送达。
进程组。shell任务控制功能允许用户同时执行并操作多条命令或管道,会将管道内的所有进程置于一个新进程组或任务中。
/proc文件系统。是一种虚拟文件系统,有目录和文件,提供一个指向内核数据结构的接口。可以通过proc/PID形式命名的目录(PID为进程ID)查看进程信息。
三. 系统编程概念
linux中系统调用服务例程的命名为sys_xxx()形式。xxx()就是系统调用。使用任何系统调用,比如在应用程序有一句execve(path, argv, envp),会先跳到C语言函数库的对应外壳函数文件中,此处是execve.c文件,找到execve(path, argv, envp),执行后会先int 0x80产生中断,进入内核态,查找对应系统调用例程(call sys_call_table[__NR_execve]),找到sys_execve()例程,执行后返回。“调用系统调用xyz()”意味着“调用外壳函数,you外壳函数去调用系统调用xyz()”。
全局变量errno在errno.h中,包含对各种错误编号定义的常量。当系统调用失败时,会将errno置为正值。
fd = open(path,flags,mode);
if (fd == -1){
if(errno == EINTR)
printf(stderr, "open was intertupted by a signal");
else{
/*other errors*/
}
}
perror()函数。会打印msg字符串,并自动打印errno的内容。
#include <stdio.h>
void perror(const char *msg);
**/proc文件系统**
虚拟的,内核在进程访问此类信息时动态创建 /proc/pid_number/代表某个具体的进程 /proc/pid_number/status /proc/pid_number/fd_number 符号链接,指向由进程打开的文件监控文件事件
inotify API:
创建inotify实例
int inotify_init(void);
创建一个监控项,包含路径何掩码。
int inotify_add_watch(int fd, const char
*pathname,uint32_t mask);
//mask针对path定义了欲监听的事件
//返回一个监控描述符对应该监控项
删除某个监控项
int inotify_rm_watch(int fd, uint32_t wd);
//成功返回0,失败返回-1
//wd是add成功后返回的监控描述符。
//为wd生成IN_IGNORED事件
inotify事件列表
书p313read()调用会阻塞, 有事件发生时返回到指定缓冲区。含有多个inotify_event结构体。
struct inotify_event{
int wd; //wd就是添加的监控项
uint32_t mask;
uint32_t cookie; //关联事件,重命名时用到
uint32_t len; // name字段长度
char name[]; // 受监控目录内发生事件的文件名。若受监控的就是文件,name为空。
};
多个inotify事件之间有填充字段。部分是name的值。
单个inotify事件的长度为sizeof(struct inotify_event)+len
执行流程:
- inotify_init()
- inotify_add_watch()
- 进入循环,用read(fd,buf,len)从inotify不断读取至buf,buf由多个struct inotify_events结构体构成。
信号
进程接收信号后的默认行为:- core:进程产生转储文件,并结束进程。
- term: 进程终止
- cont: 信号恢复了一个已停止的进程
- ignore:进程忽略了信号
pending:信号产生后会被稍后传递给某一进程。在产生和到达期间,信号处于等待(pending)状态。
安装处理器程序:通知内核应当去调用某一处理器的行为
信号已处理(handled),已捕获(caught):调用信号处理器程序以响应传递来的信号。
SIGCHLD信号:程的子进程终止时,内核会向父进程发送该信号。
SIGCONT信号:进程收到该信号会继续运行。
SIGHUP信号:终端断开时,会被发送给终端控制进程。还可用于守护进程(init,ubusd),守护进程收到该信号后,会进行重新初始化,以通过kill进程来手动向守护进程发送该信号。
SIGINT:在终端输入ctrl+c产生中断,终端把该信号输入给前台进程组,默认行为是终止进程。
SIGIO:特定的fd(如终端和套接字)发生IO事件时产生。
SIGSYS:进程的系统调用有误会产生该信号。
SIGTERM:标准的终止进程信号。会先清理临时文件释放资源,再杀掉进程。
SIGKILL:只有当SIGTERM无效时再使用这个信号,不会进行清理工作。
改变信号处理函数:signal()
原型:
#include<signal.h>
void (*signal(int sig, void (*new_handler)(int)))(int);
注意这里对signal函数运行后的返回值做声明。里层的new_handler是一个函数指针,是signal函数的第二个参数,
sig是信号编号,这里传进去,是希望改变它的信号处理函数为new_handler。
然后外层,同样定义一个函数指针,是将signal()调用的结果定义为一个函数指针,指向一个无返回值,接受一个整型参数的函数。
signal()返回的是old_handler,是一个指针,指向是sig的旧信号处理函数。
signal()调用返回SIG_ERR
简化:
typedef void (*handler_t)(int); //handler_t是种新的类型,被他声明的变量是:一个函数指针,指向无返回值,接收一个 整型参数的函数。
被简化为:
handler_t signal(int sig, handler_t handler);
使用实例:
下面演示暂时使用新的信号处理函数的过程。
void (*old_handler)(int);
old_handler = signal(SIGINT, new_handler);
if (old_handler == SIG_ERR)
errExit("signal");
/*
此时干其他的事情,中途如果有SIGINT进来,会调用new_handler处理
*/
if(signal(SIGINT, old_handler) == SIG_ERR) //换回旧的信号处理函数
errExit("signal");
handler参数值可以用下面的宏:
SIG_DFL:将信号处理 函数重置为默认值
SIG_IGN:忽略该信号。
例程,为SIGINT信号安装一个信号处理器:
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
static void
new_handler(int sig)
{
printf("signal interrupt happen");
}
int main()
{
int idx;
if(signal(SIGINT, new_handler)==SIG_ERR)
{
perror("interupt");
exit(EXIT_FAILURE);
}
for(idx = 0;;idx++)
{
printf("idx = %d", idx);
sleep(3);
}
return 0;
}
上图的程序给中断信号分配了处理器函数,程序在for里循环,一旦有中断信号产生就调用信号处理函数。
为多个信号添加同一个处理器。处理器会进行信号判断。
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
static void
new_handler(int sig)
{
if(sig == SIGINT)
{
printf("signal interrupt happen");
return;
}
else if (sig == SIGTERM)
{ printf("signal term, i will close the program now");
exit(EXIT_SUCCESS);
}
}
int main()
{
int idx;
if(signal(SIGINT, new_handler)==SIG_ERR)
{
perror("interupt");
exit(EXIT_FAILURE);
}
if(signal(SIGTERM, new_handler)==SIG_ERR) //添加第二个信号
{
perror("term");
exit(EXIT_FAILURE);
}
while (1)
{
sleep(3);
}
return 0;
}
发送信号:kill()
原型:int kill(pid_t pid, int sig);- pid
pid > 0,表示向进程号为 pid 的进程发信号
pid = 0,表示向同组进程发信号(有权限才行)
pid < -1,向进程组 |pid||pid| 发信号(有权限才行)
pid = -1,向所有进程发信号(有权限才行,早期的 POSIX 并未定义此种情况)
*sig
sig 为 0 ,通常用来测试是否有权限向进程发信号。
向4188号进程发送SIGINT中断信号。
int main(int argc, char *argv[])
{
int pid = 4188, sig = SIGINT;
s = kill(pid, sig);
if (s == 0)
{
exit(EXIT_SUCCESS);
}
else
{
if(s == ESRCH)
printf("no such process");
else if(s == EPERM)
printf("no permition");
else
{
perror("kill");
exit(EXIT_FAILURE);
}
}
return 0;
}
进程或线程调用raise向自己发送信号。
int raise(int sig); //返回负数则调用失败
显示信号描述
char *strsignal(int sig);
:返回一个指针,指向描述信号信息的字符串。
void psignal(int sig, const char *msg);
:先打印msg再显示信号信息。
信号集
信号集是一个表示多个信号的数据结构。数据类型为:sigset_t
。set就是’集’的意思。
#include <signal.h>
int sigemptyset(sigset_t *set); //初始化一个未包含任何成员的信号集
int sigfillset(sigset_t *set); //初始化一个包含所有信号的信号集
int sigaddset(sigset_t *set, int sig); //向信号集添加一个信号
int sigdelset(sigset_t *set, int sig); //从信号集删除一个信号
以上函数返回0表示成功,返回-1表示失败。
信号掩码(阻塞信号传递)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
返回0表示成功,返回-1失败。
作用是给将set信号集添加至信号掩码或从信号掩码接触。在信号掩码内的信号集们都会被阻塞。
how -- SIG_BLOCK 将set添加至信号掩码。
-- SIG_UNBLOCK 将set从信号掩码解除
-- SIG_SETMASK 将信号掩码赋值为set
oldset -- 保存之前的信号掩码
当set参数为空时,how参数将被忽略,此时就可以用来获取当前信号掩码
每个进程都有各自的信号掩码。
下面例程演示:暂时阻塞中断信号,以让不能被中断的程序执行完,然后接触对中断信号的阻塞。
int main(int argc, char *argv[])
{
_sigset_t set, prevmask;
sigempty(&set); //初始化信号集
sigaddset(&set, SIGINT); //添加将被阻塞的信号
if(sigprocmask(SIG_BLOCK, &set, &prevmask) == -1) //将信号集加入阻塞掩码
//信号集的信号都将被阻塞
{
perror("sigprocmask");
exit(EXIT_FAILURE);
}
/*
运行不应该被中断的代码
*/
if(sigprocmask(SIG_SETMASK, &prevmask, NULL) == -1) //恢复旧的阻塞掩码
{
perror("sigprocmask");
exit(EXIT_FAILURE);
}
return 0;
接收进程收到的同一种信号数量远小于发送进程发送的数量。
sender
关键用到kill(pid, sig)来向制定进程发送消息
receiver
先为每个信号绑定处理器。
再阻塞所有信号并进入睡眠,睡醒后读出睡眠期间被阻塞的信号。
然后unblock所有信号,进入while循环,等待SIGINT到来,到来后会触发对应的处理器。
sigaction
是signal的优化版原型:
int sigaction(int sig, const struct sigaction *act. const struct sigaction *oldact);
struct sigaction{
void (*sa_handler)(int); //处理器函数
sigset_t sa_mask; //当处理器函数被调用时,sa_mask信号集内的信号都会被阻塞。
int sa_flags;
};
pause
原型:int pause(void);
永远返回-1,并且把errno设为EINTER。
调用后进程会暂停执行,直到处理器函数被调用,即信号到达,会产生中断,结束pause的执行。