Linux进程

本文发布时间: 2019-Mar-22
Linux进程进程是Linux中事务管理的基本单元,所有的进程都拥有自己的独立处理环境和系统资源,并且各进程之间不可以直接访问对方的资源,进程之间的交流需要通过特定的机制(IPC)。在Linux系统的内核头文件中(/usr/src/kernels/内核版本/include/linux/sched.h)定义了进程控制块(PCB)结构体struct task_struct来管理每个进程的资源。进程资源分为两个部分:内核空间进程资源、用户空间进程资源内核空间进程资源:PCB的相关信息,控制块本身、打开的文件表项、当前目录、当前终端信息、线程基本信息、可访问的内存空间、PID、PPID、UID、EID等。用户进程空间资源:通过成员mm_struct映射的内存空间,实际上就是进程的代码段、数据段、堆、栈、可访问的共享库内存空间。这些资源在进程退出的时候主动释放。在进程运行时,可以通过查看/proc/{pid}/maps文件查看可以访问的地址空间。进程的状态:就绪、运行、等待(可中断、不可中断)、停止、僵死在/usr/src/kernels/内核版本/include/linux/sched.h头文件中定义了进程的状态/* * Task state bitmask. NOTE! These bits are also * encoded in fs/proc/array.c: get_task_state(). * * We have two separate sets of flags: task->state * is about runnability, while task->exit_state are * about the task exiting. Confusing, but this way * modifying one set can't modify the other one by * mistake. */#define TASK_RUNNING0#define TASK_INTERRUPTIBLE1#define TASK_UNINTERRUPTIBLE2#define __TASK_STOPPED4#define __TASK_TRACED8/* in tsk->exit_state */#define EXIT_ZOMBIE16#define EXIT_DEAD32/* in tsk->state again */#define TASK_DEAD64#define TASK_WAKEKILL128#define TASK_WAKING256#define TASK_PARKED512#define TASK_STATE_MAX1024进程之间状态的转换图如下:进程的属性进程本身的属性:PID(进程号)、PPID(父进程号)、PGID(进程组号)、SID(会话session ID)调用getpid()函数可以获取当前进程的PID,该函数在/usr/include/unistd.h头文件中声明;调用getppid()函数可以获取当前进程父进程的PID,该函数在/usr/include/unistd.h头文件中声明;调用getpgid(_pid_t _pid)函数可以获取指定进程_Pid的进程组号,该函数在/usr/include/unistd.h头文件中声明;调用getsid(_pid_t _pid)函数可以获取指定进程_Pid的会话号SID(会话是一个或者多个进程的集合),该函数在/usr/include/unistd.h头文件中声明;进程的用户属性:RUID(真实用户ID)、RGID(真是用户组ID)、EUID(有效用户ID)、EGID(有效用户组ID)调用getuid()函数可以获取该进程的真实用户RUID(执行此程序的用户,创建该进程用户的UID通ky"http:///qq/" target="_blank" class="keylink">qq0y734s8y1xFJVSUSjqaOsuMO6r8r91NovdXNyL2luY2x1ZGUvdW5pc3RkLmjNt87EvP7W0Mn5w/ejuzxicj4KPC9wPgo8cD6199PDZ2V0ZXVpZCgpuq/K/b/J0tS78cih09DQp9PDu6dJRKOsuMO6r8r91NovdXNyL2luY2x1ZGUvdW5pc3RkLmjNt87EvP7W0Mn5w/ejuzwvcD4KPHA+tffTw2dldGdpZCgpu/HIob34s8y1xNPDu6fX6UlEo6i0tL2ovfizzLXE08O7p8v51Nq1xNfpusWjqaOsuMO6r8r91NovdXNyL2luY2x1ZGUvdW5pc3RkLmjNt87EvP7W0Mn5w/ejuzwvcD4KPHA+tffTw2dldGVnaWQoKbvxyKHT0NCnvfizzNPDu6fX6UlEo6y4w7qvyv3U2i91c3IvaW5jbHVkZS91bmlzdGQuaM23zsS8/tbQyfnD96O7PC9wPgoKPHA+PHN0cm9uZz69+LPMtcS53MDtPC9zdHJvbmc+PC9wPgo8cD48c3Ryb25nPrS0vai9+LPMZm9yayC6zSB2Zm9yazwvc3Ryb25nPjwvcD4KPHA+1NpMaW51eLu3vrPPwqOstLS9qL34s8y1xNb30qq3vbeoyse199PDZm9ya7qvyv2ho0xpbnV4z8K1xMv509C9+LPMtrzKx9PJaW5pdL34s8yjqLXa0ru49r34s8xQSUQ9MaOp1rG907vy1d+85L3TtcS0tL2oo6xmb3Jruq/K/dTaL3Vzci9pbmNsdWRlL3VuaXN0ZC5ozbfOxLz+1tDJ+cP3o7s8L3A+CjxwPsj0Zm9ya7qvyv2199PDo6zEx8O0vauy+sn6wb249r34s8y31tano6i4uL34s8y6zdfTvfizzKOpo6zU2ri4vfizzNbQt7W72NfTvfizzLXESUSjqLTz09owo6mjrNTa19O9+LPM1tC3tbvYMKO7yPS199PDyqew3KOs1Nq4uL34s8zW0Le1u9gwo7s8L3A+CjxwPs/Cw+bKx7S0vai199PDZm9ya7qvyv21xLPM0PI8L3A+CjxwPjwvcD4KPHByZSBjbGFzcz0="brush:java;">#include<stdio.h>#include<unistd.h>int main(){ pid_t pid; if( (pid=fork()) == -1 ) //call fork() and test if succeed { printf("fork error!"); return -1; } else if(pid == 0) //in sub thread { printf("This is sub thread.\n"); printf("in sub : My PID = %d\n",getpid()); printf("in sub : My PPID = %d\n",getppid()); } else //in father thread { printf("This is father thread.\n"); printf("in father : My PID = %d\n",getpid()); } return 0;}子进程与父进程子进程将复制父进程用户空间的所有信息(文件缓冲区、代码段、数据段、BSS段、堆、栈、文件描述符),其中复制的仅仅是文件描述符,但是对于与文件描述符关联的内核文件表项(struct file结构体)则是采用的是共享的方式。下面的程序中,虽然子进程复制了父进程的文件描述符fd,但是和父进程操作的是同一个文件#include <sys/types.h>#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <stdlib.h>int main(int argc,char *argv[]){ pid_t pid; int fd; int i=1; int status; char *ch3="hello"; char *ch4="world"; char *ch3="IN"; if((fd=open("test.txt",O_RDWR|O_CREAT,0644))==-1)//打开一个文件 { perror("parent open"); exit(EXIT_FAILURE); } if(write(fd,ch3,strlen(ch3))==-1)//父进程向文件中写入字符串1 { perror("parent write"); exit(EXIT_FAILURE); } if((pid=fork())==-1)//创建一个子进程 { perror("fork"); exit(EXIT_FAILURE); } else if(pid==0) //在子进程中 { i=2; printf("in child\n"); printf("i=%d\n",i); if(write(fd,ch4,strlen(ch4))==-1)//子进程复制了父进程的fd,然后向文件中写入字符串2 perror("child write"); return 0; } else { sleep(1);//休眠1秒钟,等待子进程执行完毕 printf("in parent\n"); printf("i=%d\n",i); if(write(fd,ch3,strlen(ch3))==-1)//父进程写入字符串3 perror("parent,write"); wait(&status);//等待子进程结束 return 0; }}使用vfork创建进程使用fork创建进程会复制父进程用户空间的大量数据,有些场景这样做是极大的浪费,所以使用vfork创建新进程时并不复制父进程的地址空间,而是在必要的时候才申请新的存储空间,vfork比fork可以极大程度的提高性能。vfork只在需要的时候复制,一般采用和父进程共享所有资源的方式处理。下面的程序说明了vfork创建的子进程和父进程共享数据段和代码段。#include<unistd.h>#include<error.h>#include<sys/types.h>#include<stdio.h>#include<stdlib.h>int glob=6;int main(){ int var; pid_t pid; var=88; printf("in beginning:\tglob=%d\tvar=%d\n",glob,var); if((pid=vfork())<0) { perror("vfork"); exit(EXIT_FAILURE); } else if(pid==0) { printf("in child,modify the var:glob++,var++\n"); glob++; var++; printf("in child:\tglob=%d\tvar=%d\n",glob,var); _exit(0); } else { printf("in parent:\tglob=%d\tvar=%d\n",glob,var); return 0; }}上述程序共享glob和var变量,如果把上述中的vfork改为fork结果将会不一样。结束进程结束进程意味着要回收进程的资源,又可以分为回收进程用户空间的资源和回收进程内核空间的资源回收用户进程空间的资源进程在正常退出前都需要执行退出处理函数,刷新流缓冲区等操作,然后释放用户进程空间的所有资源,而进程在内核中的资源PCB并不会立即释放,仅仅调用退出函数而没有回收内核资源PCB的进程是一个僵死进程。显示的调用exit或者_exit系统调用可以结束进程。调用exit将以反序的方式执行由on_exit函数和atexit函数注册的清理函数,同时刷新缓冲区,并把退出状态status返回给父进程。_exti是直接退出,仅仅把退出状态交给父进程,不会进行清理工作和刷新缓冲区。注意:exit和return的区别,exit退出当前进程,而return仅仅退出当前函数。回收内核空间的资源回收内核空间的资源不是由退出进程本身完成的,而是由该进程的父亲进程完成的。调用wait函数的父进程将阻塞式的等待该进程的任意一个子进程结束后,回收该子进程的内核空间的资源,wait函数定义在/usr/include/sys/wait.h头文件中。还可以使用waitpid函数等待指定子进程结束,其定义在/usr/include/sys/Wait.h中孤儿进程和僵死进程孤儿进程:因为父进程退出而导致一个子进程被init进程收养的进程称为孤儿进程,即孤儿进程的父进程改为init进程。僵死进程:进程已经退出(用户空间的资源已经被回收),但是其父亲进程还没有回收内核资源的进程称为僵死进程,即该进程在内核空间的PCB没有被释放。下面的程序演示了一个孤儿进程#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){ pid_t pid; if((pid=fork())==-1) perror("fork"); else if(pid==0) { printf("sub : pid=%d,ppid=%d\n",getpid(),getppid()); sleep(2); printf("sub : pid=%d,ppid=%d\n",getpid(),getppid()); } else { printf("father : pid=%d,ppid=%d\n",getpid(),getppid()); sleep(1); exit(0); }}下面的程序演示了一个僵死进程#include<stdio.h>#include<unistd.h>#include<stdlib.h>int main(){ pid_t pid; if((pid=fork())==-1) perror("fork"); else if(pid==0) { printf("child_pid pid=%d\n",getpid()); exit(0); } sleep(3); system("ps"); exit(0);}程序的运行结果:运行结果中的PID=10375的进程僵死,如果此时打开/proc/10375/maps(该进程的用户进程空间信息),可以发现为空,也就是用户进程空间资源已经被回收。守护进程守护进程是一种在后台运行的特殊进程,它脱离终端,从而可以避免守护进程被任何终端信号打断,它执行产生的信息也不在任何终端显示。守护进程周期性的执行某种任务或等待处理某些发生的事件,Linux的大多数服务器就是使用守护进程实现的,比如web服务器httpd等。一般情况下,守护进程可以通过以下方式启动:(1)在系统启动时启动脚本,通常放在/etc/rc.d目录中;(2)利用inet超级服务器启动,如telnet等;(3)由cron命令定时启动以及在终端用nohup命令启动的进程也是守护进程。创建一个守护进程需要遵循以下要点:(1)屏蔽一个有关控制终端操作的信号(2)在后台运行。方法是在进程中创建子进程,并使父进程终止,使其在子进程中后台运行。(3)脱离终端控制和进程组,这是因为一个进程属于一个进程组,同进程组的进程共享一个控制终端,而一个进程关联的控制终端和进程组通常是从父进程继承下来的,因此子进程仍然受到父进程终端的影响。可以使用setsid()使子进程成为一个新的会话组长和进程组长,与原来的进程组脱离关系。(4)禁止进程重新打开控制终端。进程已经成为一个无终端的会话组长,但是仍然有权限申请一个终端,只有会话组长才能打开终端。采用的办法是再创建一个子进程,并让父进程退出,该子进程就不再是会话组长了,而且父进程也没有终端控制。(5)关闭打开的文件描述符。子进程会从父进程复制文件描述符,这对守护进程没有意义,反而会浪费系统资源,造成进程所占的文件系统无法卸载。(6)改变当前工作目录。进程活动的时候,进程工作目录所在的文件系统不能卸载,因此一般将守护进程的工作目录设置为合适的目录。(7)重设文件创建的掩码。进程从父进程那里继承文件创建掩码umask,它可能修改守护进程所创建的文件的存储权限,一般将文件创建掩码清除。(8)处理SIGCHLD信号(子进程退出信号)。


(以上内容不代表本站观点。)
---------------------------------
本网站以及域名有仲裁协议。
本網站以及域名有仲裁協議。

2024-Mar-04 02:10pm
栏目列表