Linux之进程

linu之进程详解

进程控制块(PCB)

在Linux中task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。
进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。
PCB包含信息:1、进程状态(state);2、进程标识信息(uid、gid);3、定时器(time);4、用户可见寄存器、控制状态寄存器、栈指针等(tss)

每个进程都有一个非负的唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。
除了PID,每个进程还有一些其他的标识符
Linux之进程_第1张图片
Linux之进程_第2张图片
Linux之进程_第3张图片

进程的创建

新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。所谓创建,实际上是“复制”

子进程刚开始,内核并没有为它分配物理内存,而是以只读的方式共享父进程内存,只有当子进程写时,才复制。即“copy-on-write”。
fork都是由do_fork实现的,do_fork的简化流程如下图:
Linux之进程_第4张图片

fork函数

在这里插入图片描述
==fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。==程序员可以根据返回值的不同让父进程和子进程执行不同的代码。
一个形象的过程:
Linux之进程_第5张图片

#include 
#include 
#include 

int main()
{
 pid_t pid;
 char *message;
 int n = 0;
 pid = fork();
 while(1){
 if(pid < 0){
 perror("fork failed\n");
 exit(1);
 }
 else if(pid == 0){
 n--;
 printf("child's n is:%d\n",n);
 }
 else{
 n++;
 printf("parent's n is:%d\n",n);
 }
 sleep(1);
 }
 exit(0);
}

运行结果:
Linux之进程_第6张图片

可以发现子进程和父进程之间并没有对各自的变量产生影响。
一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。

什么时候使用fork呢?

一个父进程希望子进程同时执行不同的代码段,这在网络服务器中常见——父进程等待客户端的服务请求,当请求到达时,父进程调用fork,使子进程处理此请求。
一个进程要执行一个不同的程序,一般fork之后立即调用exec

进程终止

从main返回,等效于调用exit
1.调用exit
exit 首先调用各终止处理程序,然后按需多次调用fclose,关闭所有的打开流。
2.调用_exit或者_Exit
最后一个线程从其启动例程返回
3.最后一线程调用pthread_exit

wait和waitpid函数

wait用于使父进程阻塞,等待子进程退出;waitpid有若干选项,如可以提供一个非阻塞版本的wait,也能实现和wait相同的功能,实际上,linux中wait的实现也是通过调用waitpid实现的。
waitpid返回值:正常返回子进程号;使用WNOHANG且没有子进程退出返回0;调用出错返回-1;
运行如下演示程序
Linux之进程_第7张图片

#include 
#include 
#include 
#include  

int main()
{
        pid_t pid0,pid1;
        pid0 = fork();
        if(pid0 < 0){
                perror("fork");
                exit(1);
        }
        else if(pid0 == 0){
                sleep(5);
                exit(0);//child
        }
        else{
                do{
                        pid1 = waitpid(pid0,NULL,WNOHANG);
                        if(pid1 == 0){
                                printf("the child process has not exited.\n");
                                sleep(1);
                        }
                }while(pid1 == 0);
                if(pid1 == pid0){
                        printf("get child pid:%d",pid1);
                        exit(0);
                }
                else{
                        exit(1);
                }
        }
        return 0;
}



当把第三个参数WNOHANG改为0时,就不会有上面五个显示语句了,说明父进程阻塞了。



a.out 的代码如下:


#include 
void main()

{
        printf("hello WYJ\n");
}

 

process.c的代码如下:

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
        pid_t pid_1,pid_2,pid_wait;
        pid_1 = fork();
        pid_2 = fork();
        if(pid_1 < 0){
                perror("fork1 failed\n");
                exit(1);
        }else if(pid_1 == 0 && pid_2 != 0){//do not allow child 2 to excute this process.
                if(execlp("./a.out", NULL) < 0){
                        perror("exec failed\n");
                }//child;       
                exit(0);
        }
        if(pid_2 < 0){
                perror("fork2 failded\n");
                exit(1);
        }else if(pid_2 == 0){
                sleep(10);
        }
        if(pid_2 > 0){//parent 
                do{
                        pid_wait = waitpid(pid_2, NULL, WNOHANG);//no hang
                        sleep(2);
                        printf("child 2 has not exited\n");
                }while(pid_wait == 0);
                if(pid_wait == pid_2){
                        printf("child 2 has exited\n");
                        exit(0);
                }else{
                //      printf("pid_2:%d\n",pid_2);
                        perror("waitpid error\n");
                        exit(1);

               }
        }
        exit(0);
}

运行结果:
Linux之进程_第8张图片

编写一个多进程程序:该实验有 3 个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停 5s 之后异常退出,父进程并不阻塞自己,并等待子进程的退出信息,待收集到该信息,父进程就返回。

#include
#include
#include
#include
#include
#include
int main()
{
 pid_t child1,child2,child;
 if((child1 = fork()) < 0){
 perror("failed in fork 1");
 exit(1);
 }
 if((child2 = fork()) < 0){
 perror("failed in fork 2");
 exit(1);
 }
 if(child1 == 0){
 //run ls -l
 if(child2 == 0){
 printf("in grandson\n");
 }
 else if(execlp("ls", "ls", "-l", NULL) < 0){
 perror("child1 execlp");
 }
 }
 else if(child2 == 0){
 sleep(5);
 exit(0);
 }
 else{
 do{
 sleep(1);
 printf("child2 not exits\n");
 child = waitpid(child2, NULL, WNOHANG);
 }while(child == 0);
 if(child == child2){
 printf("get child2\n");
 }
 else{
 printf("Error occured\n");
 }
 }
}

Linux之进程_第9张图片

init进程成为所有僵尸进程(孤儿进程)的父进程

僵尸进程

在进程调用了exit之后,该进程并非马上就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。

子进程结束之后为什么会进入僵尸状态? 因为父进程可能会取得子进程的退出状态信息。

如何查看僵尸进程?

linux中命令ps,标记为Z的进程就是僵尸进程。

执行下面一段程序:

#include 
#include 
int main()
{
 pid_t pid;
 pid = fork();
 if(pid < 0){
 printf("error occurred\n");
 }else if(pid == 0){
 exit(0);
 }else{
 sleep(60);
 wait(NULL);
 }
}

在这里插入图片描述

ps -ef|grep defunc可以找出僵尸进程

ps -l 可以得到更详细的进程信息

运行结果显示:
Linux之进程_第10张图片
运行两次之后发现有两个Z进程,然后等待一分钟后,Z进程被父进程回收。

其中S表示状态:

O:进程正在处理器运行

S:休眠状态

R:等待运行

I:空闲状态

Z:僵尸状态

T:跟踪状态

B:进程正在等待更多的内存分页

C:cpu利用率的估算值

收集僵尸进程的信息,并终结这些僵尸进程,需要我们在父进程中使用waitpid和wait,这两个函数能够手机僵尸进程留下的信息并使进程彻底消失

守护进程Daemon

是linux的后台服务进程。它是一个生存周期较长的进程,没有控制终端,输出无处显示。用户层守护进程的父进程是init进程。
守护进程创建步骤:
1、创建子进程,父进程退出,子进程被init自动收养;fork exit
2、调用setsid创建新会话,成为新会话的首进程,成为新进程组的组长进程,摆脱父进程继承过来的会话、进程组等;setsid
3、改变当前目录为根目录,保证工作的文件目录不被删除;chdir(“/”)
4、重设文件权限掩码,给子进程更大的权限;umask(0)
5、关闭不用的文件描述符,因为会消耗资源;close

一个守护进程的实例:每隔10s写入一个“tick

#include
#include
#include
#include
#include
#define MAXFILE 65535

int main()
{
 int fd,len,i;
 pid_t pid;
 char *buf = "tick\n";
 len = strlen(buf);
 if((pid = fork()) < 0){
 perror("fork failed");
 exit(1);
 }
 else if(pid > 0){
 exit(0);
 }
 setsid();
 if(chdir("/") < 0){
 perror("chdir failed");
 exit(1);
 }
 umask(0);
 for(i = 0; i < MAXFILE; i++){
 close(i);
 }
 while(1){
 if((fd = open("/tmp/dameon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0){
 perror("open log failed");
 exit(1);
 }
 write(fd, buf, len+1);
 close(fd);
 sleep(10);
 }
}

Linux之进程_第11张图片

#include
#include
#include
#include
#include
#include
#define MAXFILE 65535
 
int main()
{
 int fd,len,i;
 pid_t pid,child;
 char *buf = "tick\n";
 len = strlen(buf);
 if((pid = fork()) < 0){
 perror("fork failed");
 exit(1);
 }
 else if(pid > 0){
 exit(0);
 }
 openlog("Jack", LOG_PID, LOG_DAEMON);
 if(setsid() < 0){
 syslog(LOG_ERR, "%s\n", "setsid");
 exit(1);
 }

 if(chdir("/") < 0){
 syslog(LOG_ERR, "%s\n", "chdir");
 exit(1);
 }
 umask(0);
 for(i = 0; i < MAXFILE; i++){
 close(i);
 }
 if((child = fork()) < 0){
 syslog(LOG_ERR, "%s\n", "fork");
 exit(1);
 }
 if(child == 0){
 //printf("in child\n");//can not use terminal from now on.
 syslog(LOG_INFO, "in child");
 sleep(10);
 exit(0);
 }
 else{
 waitpid(child, NULL, 0);
 //printf("child exits\n");//can not use terminal from now on.
 syslog(LOG_INFO, "child exits");
 closelog();
 while(1){
 sleep(10);
 }
 }

}

真正编写调试的时候会发现需要杀死守护进程。
如何杀死守护进程?
ps -aux 找到对应PID
kill -9 PID

你可能感兴趣的:(linuz之进程,linux之进程)