Linux进程学习—进程的创建、控制与退出
本文记录一些Linux学习过程中关于进程创建与控制的相关函数与概念,主要包括fork、exec函数族、system、popen的使用示例。
1、进程的创建
1.1进程定义
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
1.2进程创建
实际操作过程中可以利用fork或vfork函数实现进程的创建,进程创建后分为子进程与父进程,函数的两个返回值分别为0和子进程的进程PID号。由此可以判定返回值为0的进程为子进程;返回值为非0值的进程为父进程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
int key=fork();
if(key>0)
{
printf("father pid is :%d \n",getpid());
}
else
{
printf("child pid is :%d \n",getpid());
}
return 0;
}
同时,需要注意fork和vfork两个函数在内存空间与执行次序两方面有所区别:
1. 内存空间
fork (): 子进程拷贝父进程的数据段,代码段
vfork(): 子进程与父进程共享数据段
2. 执行次序
fork (): 父/子进程的执行次序不确定
vfork(): 子进程先运行,在调用exec 或exit 之前与父进程数据是共享的, 子进程调用exec或exit 之后,父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
2、进程退出
正常退出:
main函数调用return
进程调用_exit() 或者 _Exit(),系统调用
进程调用exit(),标准C库( exit是_exit和_Exit的封装,前者是处理完缓存区中的内容再退出,而后两者是直接退出)
异常退出:
调用abort
当进程收到某些信号时,如ctrl + c
最后一个线程对取消(cancellation)请求做出响应
不管是正常退出还是异常退出,进程退出时都会将打开的描述符关闭,并且释放存储器。值得注意的是子进程终止进程时,需要在父进程中调用wait或waitpid函数来取得子进程的运行状态(exit的参数)。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt=0;
int status=10;
pid=fork();//创建子进程 子进程中的返回值是0,父进程中的返回值是子进程的pid号
if(pid>0)//父进程
{
wait(&status);
// waitpid(pid,&status,WNOHANG);
printf("father pid is :%d,status:%d \n",getpid(),WEXITSTATUS(status));
while(1)
{
printf("cnt=%d\n",cnt);
printf("This is Father print,pid=%d\n",getpid());
sleep(1);
}
}
else//pid==0子进程
{
while(1)
{
printf("child pid is :%d \n",getpid());
cnt++;
if(cnt == 5)
{
exit(1);
}
}
}
return 0;
} 39,1-8 全部
需要注意的是:
子进程退出后状态不被收集(wait),最终会变成僵尸进程
子进程尚未结束而父进程已经over,则会变成孤儿进程
3、进程控制
3.1、创建进程的目的
1.一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的------父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
2.一个进程要执行一个不同的程序,这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec
3.2、exec函数族
用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
头文件
#include <unistd.h>
函数原型
**1.execl** int execl(const char *path, const char *arg, ...);
**2.execlp** int execlp(const char *file, const char *arg, ...);
**3.execv** int execv(const char *path, char *const argv[]);
**4.execvp** int execvp(const char *file, char *const argv[]);
**5.execle** int execle(const char *path, const char *arg,
..., char * const envp[]);
**6.execvpe** int execvpe(const char *file, char *const argv[],char *const envp[]);
返回值
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
exec使用示例
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("/bin/ls","ls","-l",NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("/bin/date","date",NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
3.3 system函数
源码
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork()) < 0){
status = -1;
}else if(pid == 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-exit(127);
}else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTER){
status = -1;
break;
}
}
}
return status;
}
system函数使用实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
if(system("date") == -1)
{
printf("execl failed!\n");
perror("why");
}
return 0;
}
3.4 popen函数
#include <stdio.h>
FILE *popen( const char* command, const char* mode )
相对于system函数,popen可以获取返回值便于读取进程操作结果
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char temp[1024]={
"0"};
system("date");
FILE *Pid=popen("ps","r");
int nread=fread(temp,1,1024,Pid);
printf("fread number is :%d,conclude is :%s\n",nread,temp);
return 0;
}