Linux下进程控制
进程的基本知识 http://www.cnblogs.com/chaguang/p/7818440.html
在linux下init进程是所有进程的父进程
获取进程标识符的相关函数:
- pid_t getpid() 获取进程id
- pid_t getppid() 获取父进程id
- pid_t getpgrp() 获取进程组id
获取进程用户标识号的相关函数:
- uid_t getuid() 返回进程的用户ID
- uid_t geteuid() 返回进程的有效用户ID
- gid_t getgid() 返回进程的组ID
- gid_t get egid() 返回进程的有效组ID
进程的创建
- pid_t fork()
创建子进程,并且复制父进程的数据,堆栈段,进程环境,但是子进程拥有自己的进程环境。若成功则返回两个值,子进程返回0,父进程返回子进程标记;否则,出错返回-1
头文件:<sys/types.h>以及<unistd.h>
写时拷贝机制
由于子进程需要复制父进程的资源,,刚开始创建的时候,可以不需要,于是出现了写时拷贝机制,创建的子进程的时候不复制相关资源,如果是读取的话,访问相同的物理内存,如果有写操作的话,就会先复制资源,然后再写
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<unistd.h> 4 5 int main() 6 { 7 pid_t processId; 8 //创建一个子进程 9 processId=fork(); 10 printf("processId=%d\n",processId); 11 if(processId==0) 12 { 13 execlp("ls","ls","-al",NULL); 14 printf("this is a child process[%d]\n",getpid()); 15 } 16 else 17 { 18 printf("this is a father process[%d]\n",getpid()); 19 } 20 sleep(2); 21 printf("the function ending......\n"); 22 return 0; 23 } 24 25 26 ###########结果############ 27 processId=3736 28 this is a father process[3735] 29 processId=0 30 total 84 31 drwxrwxr-x 2 linux linux 4096 Mar 6 22:46 . 32 drwxr-xr-x 6 linux linux 4096 Feb 8 15:45 .. 33 -rwxrwxr-x 1 linux linux 9992 Feb 8 16:25 fork 34 -rwxrwxr-x 1 linux linux 9992 Mar 6 22:27 fork2 35 -rw-rw-r-- 1 linux linux 385 Mar 6 22:27 fork2.c 36 -rw-rw-r-- 1 linux linux 4280 Mar 6 22:27 fork2.o 37 -rw-rw-r-- 1 linux linux 623 Feb 8 16:25 fork.c 38 -rw-rw-r-- 1 linux linux 4640 Feb 8 16:25 fork.o 39 -rw-rw-r-- 1 linux linux 210 Feb 8 16:07 makefile 40 -rwxrwxr-x 1 linux linux 9984 Mar 6 22:46 vfork 41 -rw-rw-r-- 1 linux linux 414 Mar 6 22:46 vfork.c 42 -rw-rw-r-- 1 linux linux 4176 Mar 6 22:46 vfork.o 43 the function ending......
- pid_t vfork()
vfork共享父进程的资源,调用后,父进程会阻塞,直到子进程调用exec()或者_exit(),注意不能使用return返回或者使用exit()退出
exit(),return,_exit()的区别
-
- exit():终止进程前,关闭文件,清理I/O
- return:释放句柄,变量,弹出函数调用栈,回到上一级的函数
- _exit():直接退出,不关闭文件,不清理I/O
由上面可以看出,如果使用vfork创建的进程是不可以使用exit和return退出,因为vfork创建的子进程的资源是和父进程的共享,子进程如果调用exit和return就会破坏父进程的资源,从而是父进程不能正常运行。不过由于fork出现了写时拷贝机制,所以我们现在一般用的还是fork
exec函数族
执行另一个程序作为源程序的子进程,exec函数调用成功之后,系统会把新进程的地址空间替代调用进程的地址空间,并装入新进程内容中
- int execl(const char* path,const char *arg,...)
- int execlp(const char* file,const char *arg,...)
- int execle(const char *path,const char *arg,...,char *const envp[])
- int execv(const char *path,const char **argv)
- int execvp(const char* file,const char **argv)
- int execve(const char *path,const char **argv,char *const envp[])
execl:入参是可变参数列表(一级指针),必须使用NULL结尾
execv:入参是参数数组(二级指针)
p后缀:搜索系统PATH,寻找可执行文件
e后缀:显示的传递环境变量
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<unistd.h> 4 5 int main() 6 { 7 pid_t processID; 8 processID=fork(); 9 //child process 10 if(processID==0) 11 { 12 printf("this is a child process[%d]\n",getpid()); 13 // execl("/bin/ls","ls","-al",NULL); 14 15 // execlp("ls","ls","-al",NULL); 16 17 // char *pList[]={"ls","-al"}; 18 // execv("/bin/ls",pList); 19 20 char *pList[]={"ls","-al"}; 21 execvp("ls",pList); 22 23 } 24 //father process 25 else 26 { 27 printf("this is a father process[%d]\n",getpid()); 28 } 29 sleep(2); 30 printf("the process[%d] end~\n",getpid()); 31 return 0; 32 }
环境变量
相关的命令:
-
- env命令:显示当前用户所有的环境变量
- export命令:修改或显示当前用户所有环境变量
- unset:删除某个环境变量
头文件:<stdlib.h>
extern char **environ:UNIX系统中环境变量指针数组
函数:
- char *getenv(char *name)
-
- 获取环境变量值,否则返回NULL
- int putenv(const char *expr)
- 设置/修改环境变量,name=value
- 只传name则删除环境变量
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<unistd.h> 4 #include<stdlib.h> 5 //导入环境变量 6 extern char **environ; 7 8 int main() 9 { 10 char **pEnv=environ; 11 while(*pEnv) 12 { 13 printf("%s\n",*pEnv); 14 pEnv++; 15 } 16 printf("***************\n"); 17 //设置环境变量 18 putenv("hello=linux"); 19 //获取对应的环境变量值 20 printf("hello=%s\n",getenv("hello")); 21 printf("PATH=%s\n",getenv("PATH")); 22 return 0; 23 }
同步父子进程wait()函数,waitpid()函数
头文件:<sys/wait.h>
pid_t wait(int *pState)
-
- 第一个字节:*pState & 0xff ----->接受到的信号值
- 第二个字节:(*pState >> 8 ) & 0xff ------>退出值
- 第三四个字节保留:0
一般用在父进程中等待回收子进程的资源,而防止僵尸进程的产生。没有子进程的话马上返回,有子进程的话,等待子进程结束,释放子进程的资源
waitpid(pid_t pid,int *pState,int option)
-
- pid
- > 0 等待特定的子进程
- =0 等待父进程对应进程组的所有子进程
- =-1 等待任意子进程
- <-1 等待进程组标志位pid的绝对值得所有子进程
- option
- 0
- 有子进程的话就阻塞,等待子进程结束
- 1[WNOHANG]
- 非阻塞调用,立即返回
- 搜集已经退出的子进程
- 如果要搜集多个子进程,需要多次调用
- 2[WUNTRACED]
- 有子进程就阻塞
- 如果waitpid之前有子进程退出,则搜集已退出的子进程并返回
- 0
- pid
waitpid(-1,pState,0)<===>wait(pState)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<sys/wait.h> 2 #include<stdio.h> 3 #include<unistd.h> 4 5 6 int main() 7 { 8 pid_t processID; 9 int state; 10 processID=fork(); 11 if(processID==0) 12 { 13 printf("linux and python\n"); 14 sleep(4); 15 } 16 else 17 { 18 printf("father process\n"); 19 wait(&state); 20 printf("wait subProcess ending\n"); 21 } 22 return 0; 23 }
杀死进程kill()
- shell命令kill:
kill -l 查看kill信号
kill -[sig] pid 结束进程,可以选择发送的信号sig
- 函数kill()
int kill(pid_t pid,int sig)
pid参数:
-
-
- pid>0 将信号传给进程识别码为pid 的进程。
- pid=0 将信号传给和当前进程相同进程组的所有进程
- pid=-1 将信号广播传送给系统内所有的进程
- pid<0 将信号传给进程组识别码为pid绝对值的所有进程
-
sig参数:可以通过kill -l查看
僵尸进程
进程已经终止,但是没有从进程表内删除,因为已经终止,所有不能接受kill信号
产生的原因
- 子进程终止的时候,会释放资源,并发送SIGCHLD信号通知父进程,父进程接受信号后调用wait返回子进程的状态,并且释放进程表资源
- 如果子进程结束的时候,父进程没有调用wait接受子进程的消息,则子进程转化为僵尸进程
- 父进程退出的时候,僵尸进程自动结束
解决方案:
- 利用wait()函数
- 托管
- 父进程先于子进程退出,则它的子进程由init进程领养,子进程的父进程ID变为1,由init释放进程表资源
- 托管技巧:fork出子进程后,在子进程中再次fork,然后退出子进程,则子子进程会被init托管,有init释放资源表
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<sys/wait.h> 6 7 int main() 8 { 9 pid_t pid; 10 //创建一个子进程 11 pid=fork(); 12 if(pid==0) 13 { 14 //在子进程下面再创建一个子进程 15 pid=fork(); 16 if(pid==0) 17 { 18 printf("this is a child child process[%d]\n",getpid()); 19 while(1); 20 } 21 else 22 { //结束掉子进程,那么子子进程由init进程领养 23 printf("child process[%d] ending ...\n",getpid()); 24 exit(0); 25 } 26 } 27 else 28 { 29 int state; 30 printf("this is a father process[%d]\n",getpid()); 31 //接收子进程的信息 32 pid=wait(&state); 33 printf("wait ending....child process[%d] end.\n",pid); 34 //父进程执行自己的代码 35 ... 36 } 37 return 0; 38 } 39
- 忽略SIGCHLD信号
- 父进程设置忽略SIGCHLD信号,子进程结束自动释放进程表资源
- 捕获SIGCHLD信号
- 父进程捕获SIGCHLD信号,并在捕获函数代码中执行wait()
守护进程daemon
守护进程(精灵进程),是一种运行在后台的特殊进程,不存在控制终端,周期性处理某项任务,守护系统正常运行
例如:
- 大部分的socket通信服务程序都以守护进程方式执行
- 以超级用户启动的(uid=0)
- 父进程为init(ppid=1)
- 无控制终端(tty=?)
- 终端进程组为-1(TPGID=-1)
守护进程daemon创建步骤:
- 后台运行
- 托管法,fork子进程后,父进程退出
- 脱离控制终端(伪终端)
- setsid()
- 创建一个新的session和进程组
- 并使用进程ID作为进程组ID,返回进程组ID/失败返回-1
- 把当前工作目录更改为/
- 防止对当前目录的某些操作不能执行
- 关闭文件描述符,重定向stdin,stdout,stderr
- fd=open("/dev/null",O_RDWR,0);
- dup2(fd,STDIN_FILENO);...
- 设置/清除文件创建掩码<sys/stat.h>
- umask(xxx) 可以设置进程创建的临时文件不被其他用户查看
- umask(0) 清除父进程继承的文件创建掩码