原文地址:http://blog.chinaunix.net/uid-27032138-id-3364542.html
同一程序中可以运行多个线程,这些线程共享内存空间,除此之外,还共享打开的文件,
但是我们要建立一个概念,对于LINUX内核而言,根本没有线程一说!
LINUX的内核线程是由kernel_thread()函数在内核态下创建的。(将创建时得到的函数永远执行下去,由一个循环组成,在需要的时候,内核线程将被唤醒执行)
内核线程VS普通进程
(1)内核线程执行的是内核中的函数,而普通线程只能通过系统调用才能执行内核函数。
(2)内核线程只运行在内核态,而普通线程即可以运行在内核状态也可以运行在用户态。
(3)由于内核线程只运行在内核态,所以内核线程只能运行在3G~4G这个地址空间内,而一个普通进程可以运行在0G~4G的地址空间内。
特殊进程:
进程0:内核是一个达成需,它可以控制硬件,并创建,运行及控制所有的进程,内核被加载到内存后,首先由完成内核初始化的stat_kernel()从无导游的创建一个内核线程swap,并设其PID=0,因为LINUX对进程和线程是统一编号,所以也叫进程0,闲逛进程。
进程0的PCB叫做init_task,在很多链表中都充当表头。
进程1:init进程,首先将它建为一个内核线程kernel_init,当调度程序选择kernel_init内核线程后,kernel_init就开始执行内核的一切初始化函数。
kernel_init如何变为用户进程?
kernel_init函数中调用了execve()系统调用,该系统调用装入用户态下可执行程序init.(init是内核线程kernel_init启动起来的一个普通进程)
fork()系统调用
在pid=fork()之前,只有一个进程执行这段代码,执行Pid=fork()之后,陷入内核,执行内核函数do_fork()函数(do_fork()函数的作用见上一篇《进程-操作系统之魂》)
exec()系统调用
多数情况下,执行完fork后,子进程需要执行与父进程不同的代码
eg:shell首先从终端读取命令,然后创建一个子进程来执行该命令,shell等待紫禁城执行完毕,然后读取下一条命令。
一个简单的shell框架:
-
while(True)
-
{
-
read_command(command,parameters);//从终端读取命令
-
if(fork()!=0)//创建子进程
-
wait(NULL);//父进程等待
-
else
-
exec(command,parameters,0);//执行命令
-
}
exec函数族执行成功后不会返回,因为调用进程的实体都已经被新的内容“取代”。只留下进程ID等一些表面上信息保持原样,看上去是旧的躯壳,却已经注入新的灵魂。只有在调用失败的时候,他们才会返回一个-1,从原程序的调用点接着往下执行。(每当有进程认为自己不能为系统和用户作出任何贡献,就发挥最后一点余热,调用任意一个exec,让自己新面貌重生)
如果一个进程想要执行另一个程序,它就可以fork出一个新近成,然后调用任何一个exec,看上去就像通过执行应用程序而产生一个新进程一样。
wait系统调用
进程一旦执行了wait,就立即阻塞自己,由wait自动分析是否当前进程某个子进程已经推出,如果它找到这样一个已经变成僵尸的紫禁城,wait就回收集子进程的信息,释放PCB。
pid_t wait(int *status)//参数用来保存收集进程推出时候的一些状态,如果不管新如何死掉,直接pid=wait(NULL),调用成功,wait会返回被收集的子进程PID,如果没有子进程,调用就失败,此时,wait返回-1
exit系统调用
用来种植一个进程,无论exit在程序中处于什么位置,只要执行到该系统调用就陷入内核,执行相应的内核函数do_exit()。回收进程相关的各种内核数据。把进程状态置为TASK_ZOMBIE,并把其所有的子进程都托福给init().最后都调用schedule()
void exit(int status)//自带整形参数,可以利用这个参数传递进程结束时候的状态。
fork() VS vfork()
fork()创建的子进程有自己独立的地址空间,不管全局或者局部,子进程与父进程的修改互不影响。
vfork()中,子进程的修改对父进程可见,而且调用vfork()的程序,都先调用子进程部分,后调用父进程部分。
守护进程
LINUX的后台服务进程,生存期长,通常独立与控制终端并周期性的执行某种任务。守护进程在系统引导装入时候启动,在系统关闭时候终止.LINUX中,每一个系统与用户进行交流的界面称为终端,当终端关闭,相应的进程也会自动关闭(守护进程却突破限制,从执行开始运转,直到整个系统关闭才退出)
创建守护进程过程:
1、创建子进程,让父进程退出。(由于守护进程脱离终端,因此,完成第一步后,就会在shell终端里造成程序已经运行完毕的假象,之后所有工作都在子进程中完成)--孤儿进程
2、在进程中创建新的会话
首先了解什么是进程组,什么是会话期
进程组:就是一个或者多个进程的集合,每一个进程由PID唯一标识,每个进程组都有一个组长进程,其组长进程号等于组ID(进程组ID不会因为组长ID退出而受到影响)
会话期:进程组的集合,开始于用户的登录,终止于用户的退出。
setsid():创建一个新会话,并且担任会话组长。
调用setsid()的三个作用:
让进程摆脱原会话控制
让进程摆脱原进程组控制
让进程摆脱原控制终端控制
为什么调用setsid()?
由于第一步创建子进程,再让父进程退出,但是父进程虽然已经推出,但是调用fork()之后,子进程拷贝了父进程的会话期,会话期、进程组、控制终端这些不能改变,因此不是真正意义上的独立开,调用setsid是想让进程完全独立。
3、改变当前目录为根目录:使用fork创建子进程承载了父进程的工作目录,由于在进程运行过程中,当前目录的所有文件系统不能卸载,这对以后的使用造成了诸多的麻烦,常用做法是让“/”作为守护进程的当前目录--常见函数为:chdir("/")
4、设置文件权限掩码:指的是屏蔽掉文件权限中对应位置,把文件权限掩码设为0,可以大大增强守护进程的灵活性。
常用umask(0)
5、关闭文件描述符:用fork()创建子进程会从父进程那里集成打开的文件,这些文件永远不会被守护进程读写,但是他们耗费系统资源,而且导致文件系统无法卸下。
6、守护进程的退出处理
signal(SIGTERM,sigterm_handler);
void sigterm_handler(int arg)
{
__running=0;
}
阅读(1917) | 评论(0) | 转发(0) |