进程系统调用
- 创建子进程
pid_t fork(void)
- 获取进程号
pid_t getpid(void)pid_t getppid(void)pid_t getpgid(pid_t pid)
- 调用进程内部执行一个可执行文件
int execl(const char *path, const char *arg, .../* (char *) NULL */)int execlp(const char *file, const char *arg, ... /* (char *) NULL */)
- 进程退出
void exit(int status)void _exit(int status)
- 进程回收
pid_t wait(int *wstatus)int *wstatus:进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
pid_t waitpid(pid_t pid, int *wstatus, int options)
进程通信方式-IPC
管道
-
分类:
- 匿名管道
- 有文件实体
- 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
- 有名管道:
- 有文件实体, 但不存储数据
- 可以在有关系,或无关的进程之间使用
- 匿名管道
-
本质:内核内存中维护的缓冲器
-
通信方式:半双工通信
-
数据结构:循环队列
管道系统调用
- 创建匿名管道:
int pipe(int pipefd[2])-
int pipefd[2]:这个数组是一个传出参数。-
pipefd[0]对应的是管道的读端 -
pipefd[1]对应的是管道的写端
-
-
- 查看管道缓冲大小函数:
long fpathconf(int fd, int name)
- 创建有名管道:
int mkfifo(const char *pathname, mode_t mode)pathname: 管道名称的路径mode: 文件的权限 和 open 的 mode 是一样的,是一个八进制的数
信号
基本概念:
-
信号:是事件发生时对进程的通知机制,有时也称之为软件中断。信号 可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
-
信号集:多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为
sigset_t- 未决信号集:从信号的产生到信号被处理前的这一段时间。
- 阻塞信号集:阻止信号被处理,但不是阻止信号产生。
- 操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。
- 若想处理未决信号集,需要先查看阻塞信号集对应信号是否被阻塞:
- 如果没有阻塞,这个信号就被处理
- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
重要的信号:
-
SIGINT- 编号:2
- 当用户按下了<Ctrl+C>组合键时,用户终端向正 在运行中的由该终端启动的程序发出此信号
- 默认动作:终止进程
-
SIGQUIT- 编号:3
- 用户按下<Ctrl+>组合键时产生该信号,用户终 端向正在运行中的由该终端启动的程序发出些信号
- 默认动作:终止进程
-
SIGKILL- 编号:9
- 无条件终止进程,可以杀死任何进程
- 默认动作:该信号不能被忽略,处理和阻塞
-
SIGSEGV- 编号:11
- 指示进程进行了无效内存访问(段错误)
- 默认动作:终止进程并产生core文件
-
SIGPIPE- 编号:13
- Broken pipe向一个没有读端的管道写数据
- 默认动作:终止进程
-
SIGCHLD- 编号:17
- 子进程结束时,父进程会收到这个信号
- 默认动作:忽略这个信号
-
SIGCONT- 编号:18
- 如果进程已停止,则使其继续运行
- 默认动作:继续/忽略
-
SIGSTOP- 编号:19
- 停止进程的执行。信号不能被忽略,处理和阻塞
- 默认动作:终止进程
信号系统调用
-
给任何的进程或者进程组pid, 发送任何的信号 sig
int kill(pid_t pid, int sig);
-
给当前进程发送信号
int raise(int sig);
-
发送SIGABRT信号给当前的进程,杀死当前进程
void abort(void);
-
设置定时器
unsigned int alarm(unsigned int seconds);
-
设置定时器
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)-
int which: 定时器以什么时间计时-
ITIMER_REAL: 真实时间,时间到达,发送SIGALRM常用 -
ITIMER_VIRTUAL: 用户时间,时间到达,发送SIGVTALRM -
ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送SIGPROF -
真实时间 = 内核时间 + 用户时间 + 消耗的时间
-
-
const struct itimerval *new_value: 设置定时器的属性struct itimerval { // 定时器的结构体 struct timeval it_interval; // 间隔时间:每隔多久定时 struct timeval it_value; // 程序开始后,延迟多长时间执行定时器 }; struct timeval { // 时间的结构体 time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒 };举例:过10秒后,每隔2秒定时一次:10s:it_value, 2s:it_interval
-
struct itimerval *old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
-
-
信号捕捉
sighandler_t signal(int signum, sighandler_t handler)-
sighandler_t handler: 捕捉到信号要如何处理-
SIG_IGN: 忽略信号 -
SIG_DFL: 使用信号默认的行为,相当于没有捕捉。 -
回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
-
void (*sighandler_t)(int)函数指针,int类型的参数表示捕捉到的信号的值。 -
需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
-
不是程序员调用,而是当信号产生,由内核调用
-
函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
-
-
-
-
将信号集设置不阻塞
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)
-
将自定义信号集中的数据设置到内核中
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
-
获取内核中的未决信号集
int sigpending(sigset_t *set)
-
信号捕捉
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)-
int signum: 需要捕捉的信号的编号或者宏值(推荐) -
const struct sigaction *act:捕捉到信号之后的处理动作struct sigaction { // 函数指针,指向的函数就是信号捕捉到之后的处理函数 void (*sa_handler)(int); // 不常用 void (*sa_sigaction)(int, siginfo_t *, void *); // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。 sigset_t sa_mask; // 使用哪一个信号处理对捕捉到的信号进行处理 // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction int sa_flags; // 被废弃掉了 void (*sa_restorer)(void); }; -
struct sigaction *oldact: 上一次对信号捕捉相关的设置,一般不使用,传递NULL
-
内存映射
- 内存映射(Memory-mapped I/O):将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
- 内存映射是进程通信(IPC)的一种方式,可以用于:
- 有关系的父子进程
- 无关的两个进程
- 内存映射通信,是非阻塞的。
内存映射系统调用:
- 将一个文件或者设备的数据映射到内存中:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);-
void *addr: NULL, 由内核指定 -
size_t length: 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
获取文件的长度:stat()lseek() -
int prot: 对申请的内存映射区的操作权限-
PROT_EXEC:可执行的权限 -
PROT_READ:读权限 -
PROT_WRITE:写权限 -
PROT_NONE:没有权限要操作映射内存,必须要有读的权限.PROT_READ、PROT_READ|PROT_WRITE
-
-
int flags:-
MAP_SHARED: 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项 -
MAP_PRIVATE:不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
-
-
int fd: 需要映射文件的文件描述符-
通过open得到,open的是一个磁盘文件
-
注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
-
prot:
PROT_READopen:只读/读写 -
prot:
PROT_READ | PROT_WRITEopen:读写
-
-
-
off_t offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移。
-
- 释放内存映射:
int munmap(void *addr, size_t length);
守护进程
- 进程组:是一组相关进程的集合,共享同一进程组标识符(PGID)
- 进程组首进程:该进程是创建该组的进程,其进程 ID 为该进程组的 ID
- 新进程会继承其父进程所属的进程组 ID
- 生命周期:其开始时间为首进程创建组的时刻,结束时间为最后一个 成员进程退出组的时刻
- 会话:是一组相关进程组的集合。
- 会话首进程是创建该新会话的进程,其进程 ID 会成为会话 ID
- 新进程会继承其父进程的会话 ID
- 任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为 后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。
系统调用
◼ 进程组:pid_t getpgrp(void);
◼ 进程组:pid_t getpgid(pid_t pid);
◼ 进程组:int setpgid(pid_t pid, pid_t pgid);
◼ 会话:pid_t getsid(pid_t pid);
◼ 会话:pid_t setsid(void);
守护进程(Daemon 进程、精灵进程)
- Linux 后台服务进程
- 生命周期很长:守护进程会在系统启动的时候被创建并一直运行直至系统被关闭
- 它在后台运行并且不拥有控制终端
- Linux 的大多数服务器就是用守护进程实现的。
- Internet 服务器 inetd
- Web 服务器 httpd
- ssh 协议 sshd
守护进程的创建步骤
- 执行一个
fork(),之后父进程退出,子进程继续执行。- 确保子进程不会成为进程组的首进程。
- 子进程调用
setsid()开启一个新会话。- 目的:脱离控制终端。创建新会话没有控制终端。
- 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
- 非必需。
- 修改进程的当前工作目录,通常会改为根目录(/)。
- 非必需。
- 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
- 非必需。
- 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
- 非必需。
- 核心业务逻辑