1、Linux的登录环境
Linux是一个多任务多用户的操作系统,其设计初衷: 就是要达成多用户同时使用单个计算机大的任务。
多用户:早期计算机资源紧张,为了让更多的人都可以使用。
多任务:服务于多用户,同时提高计算机的吞吐量。
早期登录就是通过哑终端连接登录的。
早期登录Linux模型
随着以太网的发展,Linux的出现,在进行多用户连接时候,充分使用了以太网。提高了应用层的网络服务,这些网络服务替代了之前的轮询监控串口的服务进程。ssh相对于Telnet就是加密了,更加安全。
2、进程组
在Linux下进程除了具有父子关系这样的组织关系以外,还具有分组的组织关系,任何一个进程都需要隶属于某个进程组。
每一个进程组都拥有一个独立的进程组编号,可以通过getpgid()方法得到。
每一个进程组都拥有且只拥有一个组长进程。可以通过该组长管理其组内的其它进程的统一行为。(例如:组长进程若获取一个特殊的信号,该信号可以传递给组内所有进程)。
进程组ID就是该进程组组长的进程ID。
进程组内的成员都是组长的子及子孙。
3、会话
每一个进程除了要归属一个进程组以外,还需要归属于一个会话之中。会话的概念主要是从终端登录到计算机之后得到的。
当一个终端登录到计算机之后,为了方便将不同的终端隔离开,同时又能够充分合理的管理一个终端下产生的所有进程,因此而提出了会话的概念。换句话来说,会话就是用户登录之后从登录服务进程到shell进程所组成的集合。
从逻辑上来讲,会话就控制了计算机和某一个终端的一个连续的交互过程。
一个会话通常是由多个进程组组成的,分为两部分(前台进程组、后台进程组)。
会话具有一个会话首进程。操作系统通过会话的首进程来管理整个会话中的所有进程组。
(1)、前台进程组
是和终端服务进程、bash进程捆绑在一起的,和终端直接相关。那么,终端的任何操作都会影响到所有的前台进程组。
在shell交互环境中执行一个命令,就会产生一个新的进程来执行这个命令,不仅如此,还会产生一个新的进程组,该进程组的组长就是被执行命令而形成的新进程。
前台进程组的最大问题就在于,需要看终端的脸色!终端只需要让进程组的组长停止,其内部的所有进程都会停止。
(2)、后台进程组
断开与终端的关系(并非输入、输出、错误输出关系),进程组关系,不在受制于终端而存在,这种进程与进程组被称之为后台的。
后台进程存在的理由就是,需要常驻内存,提供一个服务。
4、进程组如何创建
创建一个新的进程组的方式有2种,这2种有区别:
(1)、通过调用setpgid()能够将调用该方法的当前进程设置为新的进程组的组长。从而创建一个新的进程。
(2)、通过调用setsid()方法来创建一个新的会话,从而就会出现新的进程组,调用setsid()方法的进程就会成为会话的首进程,新进行组的组长。
5、僵尸进程
在Linux操作系统设计中,要求进程结束的时候需要告知其父进程该进程的结束,父进程也需要知道其子进程结束的状态,这是因为父进程有些时候需要根据子进程结束的状态来作一些后续的操作。
进程的消亡:
(1)、进程调用了exit()方法,通知操作系统内存,当前进程想要结束自己的生命;
(2)、操作系统此时就会立即回收进程的几乎所有的主要内容,然后告知其父进程,它的某一个子进程结束;
(3)、父进程需要明确的答复操作系统内核,已收到子进程结束的消息。否则,操作系统内核会一直保存该将要结束进程的部分PCB信息。同时将进程的状态置为defunct。这就是僵尸进程。
i>、僵尸进程是不能够被直接消除掉的。
ii>、僵尸进程的危害: 占用PCB资源(PID资源)。
iii>、子进程先于父进程结束,父进程没有收尸,就是僵尸进程。
其产生僵尸进程的代码如下:#include
#include
#include
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
printf("This is child, will finish. pid = %d\n", getpid());
}else if(pid > 0){
while(1){
printf("This is father, run allways. pid = %d childPid = %d\n", getpid(), pid);
sleep(1);
}
}else{
perror("");
}
return 0;
}
运行结果
如何解决僵尸进程的问题?避免僵尸进程的产生。
解决僵尸进程的根本就在于父进程需要处理子进程的结束。通过调用wait()函数族来进行解决。wait会等待子进程的结束,调用wait的进程会阻塞,直到接受到子进程的结束消息时才会被唤醒。父进程等待子进程的结束消息为SIGCHLD(17)信号。
解决僵尸进程的代码 :#include
#include
#include
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
int i;
for(i = 0; i
printf("This is child, will finish. pid = %d\n", getpid());
sleep(1);
}
}else if(pid > 0){
int status;
wait(&status); //很好的处理了僵尸进程。
printf("child has gone, status = %d\n", status);
while(1){
printf("This is father, run allways. pid = %d childPid = %d\n", getpid(), pid);
sleep(1);
}
}else{
perror("");
}
return 0;
}
此时就会造成父进程一开始什么也干不了,只能等待子进程的结束,自己才能开始运行。
僵尸进程的解决其实是进程回收进程。
6、孤儿进程
如果某一个进程的父进程先于自己结束,那么该进程还有父进程吗?必须有,因为在Linux下所有的进程都必须存在于整个进程树之中,不允许完全孤立的存在。
如果发生父进程先于子进程结束的情况,则将子进程过继给编号为1的进程,即就是init/systemd进程。
然而此时该进程的进程组关系已经打乱。它所在的进程组不在是其父进程或祖先进程。因为,此时子进程依然保留之前的进程组信息,但是很显然这个进程组与会话信息已经不对了。
这种进程就是孤儿进程,失去了原有的所有进程组与会话关系的进程。所以也会将init/systemd进程称之为孤儿院进程,因为它收养了很多孤儿。
因为子进程的父进程结束,所以其组的组长的信号就不能够传递给该孩子进程。从而使得该子进程就脱离了原有的进程组关系、脱离了原有的会话关系。虽然它保留了原有的进程组和会话ID,但是已经不起任何作用了。
产生孤儿进程的代码 :#include
#include
int main(void){
pid_t pid;
pid = fork();
if(pid == 0){
while(1){
printf("This is along child, pid = %d, pgid = %d sid = %d\n", getpid(), getpgid(getpid()),
getsid(getpid()));
sleep(1);
}
}else if(pid > 0){
printf("This is father, pid = %d, pgid = %d sid = %d\n", getpid(), getpgid(getpid()), getsid(getpi
d()));
}else{
perror("");
}
return 0;
}
此时孤儿进程产生,将用ctrl+c终止不下来,只能用kill PID进行杀死该孤儿进程了。
7、守护进程
守护一个服务,长期驻留在内存中提供服务,不能够受制于终端。通常指的就是操作系统中的服务进程。这些服务进程通常约定其名称最后一个字母为d。
守护进程的另外一个名字 : 精灵进程(demon)。
如何让一个进程成为守护进程? 让一个进程脱离前台进程组关系即可(这样就可以摆脱终端对它的控制)。
怎样让进程脱离前台进程组? 1)、创建一个新的会话;2)、构成孤儿进程。
通常让这两个步骤都进行。
守护进程的创建过程:pid_t pid;
pid = fork();
if(pid == 0){
setsid(); //产生会话进程
while(1){ //子进程一直在运行,迟早会构成孤儿进程。
...
}
}else if(pid > 0){
...
exit(0); //父进程结束
}else{
perror();
}
8、僵尸进程和孤儿进程的一个关联
init/systemd又叫做上帝进程,一手创造了操作系统内部的所有的进程以外,还管理了这些僵尸进程,帮助将其清除掉了。
如果一个进程下产生了僵尸子进程,当该进程结束的时候,会将僵尸进程过继给init/systemd进程,然后init/systemd进程发现过继的新子进程是僵尸进程之后,会为其收尸,从而消除掉已经存在的僵尸进程。