创建进程
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[]){
printf("father: %u self:%u\n",getppid(),getpid());
pid_t id = fork();
if(id == -1){
perror("fork");
return -1;
}
if(id == 0){//son process
//son
printf("son process: %u, fahter is:%u\n",getpid(),getppid());
}else{//parent prcoess
//father
printf("father process: %u, son is: %u\n",getpid(),id);
}
printf("son father run ....\n");
sleep(1);
return 0;
}
创建子进程时的写时复制技术
- 调用fork函数会创建一个一摸一样的子进程,但因为子进程会多次调用exec函数,出于效率考虑,linux引入了“写时复制”技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程
孤儿进程
- 父进程先于子进程结束,子进程就会变为孤儿进程,一般会由init进程收养
僵尸进程
- 子进程先于父进程结束,向父进程发送SIGNAL(17)信号,而父进程没有回收资源或在回收资源之前子进程会变为僵尸进程
孤儿进程与僵尸进程的解决方案
- kill元凶父进程(一般不用)
- 父进程和调用wait()或waitpid()去回收资源(方案不好)
- 通过信号机制,在处理函数中调用wait()去回收资源
fork与vfork的区别
- fork(): 创建的子进程会拷贝父进程的数据段,代码段
vfork(): 创建的子进程与父进程共享数据段 - fork(): 子进程与父进程的执行顺序不确定
vfork(): 保证子进程先运行 - vfork()创建的子进程保证先于父进程运行,在它调用exec过exit函数后父进程才可能被调度运行。若子进程在调用这两个函数之前依赖于父进程的进一步动作,则会导致死锁。
进程状态
进程间通信的方式
- 无名管道(队列,先进先出): 管道是半双工的,只能单向通信,实现双向通信需要两个管道,无名管道只能用于父子进程或兄弟进程中
- 有名管道: 严格遵循FIFO(是一种先进先出的我数据存储、缓冲器),能够实现同一主机上任意两个进程的通信
- 信号
- 信号量: 是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步
- 消息队列: 消息的链接表,放在内核中
- 套接字
- 共享内存: 无需复制数据,最快的一种进程间通信方式
- 内存映射: 是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
守护进程
- 是linux中的后台服务进程,它是一个生存期比较长的一个进程,通常独立于控制终端,并且周期性的执行某种任务或者发生的事件
- 调用fork()创建子进程后,父进程exit,子进程继续执行,就相当于一个孤儿进程
- 创建一个守护进程,需要遵循一定原则,以防止不必要的交互
- 首先要调用umask(0),重设文件掩码。一般为0
- 调用fork()创建子进程,然后父进程exit()
- 调用setsid(),让当前进程成为新会话的首进程
- 将工作目录更改为当前目录,chdir(“/”);
- 关闭一些没必要的文件描述符,比如标准输入输出啥的
进程的调度算法
- 先来先服务调度算法
- 每次调度都是从后备进程中调度一个或多个最先进入后备队列的进程
- 短作业优先调度算法
- 高优先级优先调度算法
- 时间片轮转法
- 多级反馈队列调度算法
共享内存
- 共享内存不同于内存映射区,它不属于任何进程,不受任何进程的生命周期影响
信号处理
- 默认处理
- 捕获并处理
- 可以在信号发生前,注册信号处理函数
- 发生信号时,就可以调用之前的信号处理函数处理
常见信号
- 2 SIGINT ctrl+c
- 3 SIGQUIT
- 9 SIGKILL
- 14 SIGALRM 时钟定时信号
- 17 SIGCHLD
- 18 SIGCONT 让暂停的进程恢复执行
- 19 SIGSTOP ctrl+z 暂停前台进程,不能被阻断
sleep和wait的区别
- sleep是一个延时函数,让进程或线程休眠数秒。休眠完毕后继续执行
- wait父进程回收子进程资源的一个调用函数,调用wait函数就会阻塞自己,直到子进程已经退出