操作系统相关知识
1. 守护进程
1.1.什么是守护进程?
主要是将服务器方面的程序给隐藏到控制端下面。比如redis的,我们启动redis-server后,他会有一个图像化的界面,如果没有开启守护进程,你在set key value 后会有一个日志的打印。
如果你不小心按到ctrl +c了,不好意思,你的服务器dump掉了。
1.2.为什么要守护进程?
就是为了解决上述1说的守护进程就是为了隐藏到控制界面下面。
试想一下,如果你的电脑调打开了很多程序,这个时候都有一个界面放到这给你看。我们可以通过指令查看一下linux下有哪些开启了守护进程。
ps -axjPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND0 1 1 1 ? -1 Ss 0 0:08 /sbin/init auto noprompt splash splash 30 2 0 0 ? -1 S 0 0:00 [kthreadd]2 3 0 0 ? -1 I< 0 0:00 [rcu_gp]2 4 0 0 ? -1 I< 0 0:00 [rcu_par_gp]2 5 0 0 ? -1 I< 0 0:00 [slub_flushwq]2 6 0 0 ? -1 I< 0 0:00 [netns]2 7 0 0 ? -1 I 0 0:06 [kworker/0:0-events]2 8 0 0 ? -1 I< 0 0:00 [kworker/0:0H-events_highpri]2 10 0 0 ? -1 I< 0 0:00 [mm_percpu_wq]2 11 0 0 ? -1 S 0 0:00 [rcu_tasks_rude_]2 12 0 0 ? -1 S 0 0:00 [rcu_tasks_trace]2 13 0 0 ? -1 S 0 0:00 [ksoftirqd/0]2 14 0 0 ? -1 I 0 0:03 [rcu_sched]2 15 0 0 ? -1 S 0 0:00 [migration/0]2 16 0 0 ? -1 S 0 0:00 [idle_inject/0]
这只是一部分,至少要有一百多个开启守护进程的进程来启动服务。比如,脏页置换,网络io,如果你启动了比如,nginx,reids等等服务器,你开启xshell服务,如果有一百多个操作窗口,你肯定是要崩溃的。
目的:就是将应用服务隐藏到控制端下面,如果想调用,或者查看在通过指令切换查看就OK了,没必要全部摆到你的应用上让你看,尤其是涉及到操作系统本身的一些守护进程,很多很复杂,也没必要展示给程序员开,同时,给你看,你也不能中断该服务啊,你要是能中断了,你操作系统也太脆弱了。
1.3.守护进程干了什么?
所谓守护进程,其实就是让应用在控制界面后台提供服务。我们可以看到有很多进程的状态都是S睡着的。
这里扩展一下,进程的状态的讲解:
1.3.1进程的状态
从理论分析,进程状态有:创建、就绪,运行,阻塞,终止
进程状态的附加符
1.4.如何创建守护进程?
进程:可以理解成上面redis-server的应用程序;
守护进程就是,要开启将上述redis-server给隐藏到后台,什么操作能够做到即能维持redis-server本身服务的不断,而又能够进行后台的转变呢?**开启子进程**使用fork函数。
具体的创建细节可以看其他的博客,这里展示不讲解。
1.4.1 守护进程创建步骤
1. 调用fork函数:创建一个子进程(其实就是父进程创建完直接自己主动退出,也就是守护进程(本质也是孤儿进程)),这个即使所谓的守护进程托管给init进程用于回收资源。
2.fork后:子进程和父进程采用写时复制的方式,并且共享文件描述符,目录等。
3.为了解决父子进程操作文件描述符同步的问题,因为父子进程共享文件,文件描述符,文件读写的偏移量,如果不分离操作,父子进程同步时很花费时间,同时父进程通常不止一个子进程,这会造成很多问题,可以想象成多线程编程了。所以,为了解决这个问题,使用了关闭不必要文件,并重新文件的掩码。
原因:你把自己的思维拉到os的级别思考问题很容易,比如:一个网络服务,一个客户端进来,子进程去处理该请求,父进程继续等待下一个服务的请求。参考案例:Nginx的主进程和woker进程。
1.5 僵尸进程、孤儿进程
1.5.1僵尸进程
僵尸:“死而未僵”,指的是父进程创建的子进程工作结束了父进程却没有回收子进程的资源。
解决方法:调用正确的系统调用回收资源就好了,属于malloc忘记free的问题。
1. 父进程通过waitid()等待子进程结束,父进程才结束。防止因为父进程退出无人回收资源。
2.waitid()其实内部调用了信号捕捉的回调函数,操作系统在子进程结束通过一个sigchild()信号来通过信号中断,让父进程去回收资源,这样确实最安全。
进程状态:Z
1.5.2 孤儿进程
孤儿:父亲突然死了。孤儿进程其实也是这个意思,父进程突然异常结束了。这个时候子进程还没有结束,所以需要指定一个父进程在子进程结束时回收资源。一般是init进程回收资源(是所有进程的父进程,还有看到的说法是可以手动指定叔父进程充当父进程,让其回收孤儿进程的资源)。
2. 进程
进程是:正在运行的程序。
进程从数据结构角度来看:
struct task_struct {// 进程的状态volatile long state;// 进程的地址空间mm_segment_t mm;// 进程的文件描述符struct files_struct *files;// 进程的信号struct signal_struct *signal;// 进程的调度信息struct sched_struct *sched;
};
2.1 进程的管理
也可以成为进程的调度。内核空间内部维护一个双向链表,存放task_struct,也成为任务队列
2.1.1 用户进程管理进程的数据结构
struct thread_info {// 进程描述符struct task_struct *task;// 进程的栈unsigned long stack;// 进程的入口点unsigned long ip;// 进程的参数unsigned long arg;// 进程的返回值unsigned long ret;// 进程的状态unsigned long state;// 进程的地址空间mm_segment_t mm;// 进程的文件描述符struct files_struct *files;// 进程的信号struct signal_struct *signal;// 进程的调度信息struct sched_struct *sched;// 进程的内核栈unsigned long kstack;// 进程的内核态入口点unsigned long kip;// 进程的内核态参数unsigned long karg;// 进程的内核态返回值
}
进程调度时,通过一个current宏来获取thread_info。然后进程进入运行状态。
可以看到,用thread_info来描述用户进程。所以,进程和线程没啥区别。
进程和线程唯一的区别:1.进程的创建会有独立的内存空间,以及文件描述符等资源。
2.线程可以理解为子进程,这个子进程没有进行fork函数时的内存的分配,文件描述符的复制以及写时复制。就是因为创建子进程要做的事情太多了,工作量太大了,父子进程协作工作,还需要cpu进行调度算法,所以才有了线程的创建。
3.进程和线程在资源的使用上,不仅仅是进程创建时,为其开辟资源这么简单,同时也体现在使用资源时,比如读a.txt,四个进程读,a.txt的引用技术只有4,而如果这个时候其中一个进程的四个线程只读,那么这个a.txt的使用技术会变成8。
2.1.2 进程的调度
常见的:先到先服务,短作业有限,高响应比,时间片轮转,多级反馈队列,CFS(linux特有的)
2.2 进程通信
2.3 进程死锁
2.4 线程和协程
2.5 线程池
2.5.1 io密集型的线程池
2.5.2cpu密集的线程池