Linux系统编程之进程
1、什么是程序,什么是进程
程序是静态的概念,gcc xxx.c –o pro 磁盘中生成pro文件,叫做程序
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程
2、什么是进程标识符
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
Pid=0: 称为交换进程(swapper)
作用—进程调度
Pid=1:init进程
作用—系统初始化
编程调用getpid函数获取自身的进程标识符;getppid获取父进程的进程标识符
3、fork()创建进程
fork用来创建一个子进程。一个程序一调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。
头文件:
#include <sys/types.h>
#include <unistd.h>
使用形式:
pid_t fork(void);
fork函数调用成功,返回两次
返回值为0, 代表当前进程是子进程
返回值为子进程的进程ID,代表当前进程为父进程
调用失败,返回-1
学习实例:
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t retpid;
int i = 0;
retpid = fork();
if(retpid == 0){
while(1){
printf("this is child pid=%d\n",getpid());
sleep(1);
i++;
if(i==3){
exit(-1);
}
}
}else{
while(1){
printf("this is father pid=%d retpid=%d i=%d\n",getpid(),retpid,i);
sleep(1);
}
}
return 0;
}
运行结果:
this is father pid=2875 retpid=2876 i=0
this is child pid=2876
this is father pid=2875 retpid=2876 i=0
this is child pid=2876
this is father pid=2875 retpid=2876 i=0
this is child pid=2876
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
this is father pid=2875 retpid=2876 i=0
^C
4、vfork()创建进程
头文件:
#include <sys/types.h>
#include <unistd.h>
使用形式:
pid_t vfork(void);
返回值:
创建成功:子进程中返回 0,父进程中返回子进程 ID。
创建失败:返回 -1
注意:
1.vfork()创建子进程,在调用exec()之前或exit()之前,子进程与父进程共享数据段(与fork()不同,fork要拷贝父进程的数据段,堆栈段)
2.调用vfork()后,子进程先执行,父进程被挂起,直到子进程调用了exec或exit之后,父进程才执行。
学习例程:
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid1;
pid_t pid2;
pid_t retpid;
int i = 0;
retpid = vfork();
if(retpid == 0){
while(1){
printf("this is child pid=%d\n",getpid());
sleep(1);
i++;
if(i==3){
exit(-1);
}
}
}else{
while(1){
printf("this is father pid=%d retpid=%d i=%d\n",getpid(),retpid,i);
sleep(1);
}
}
return 0;
}
运行结果;
this is child pid=2757
this is child pid=2757
this is child pid=2757
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
this is father pid=2756 retpid=2757 i=3
^C
5、进程退出
正常退出:
(1)main函数调用return
(2)进程调用exit(),标准c库
(3)进程调用_exit()或者_Exit(),属于系统调用
使用方法:
NAME
exit -cause normal process termination
SYNOPSIS
#include<stdlib. h>
void exit(int status);
NAME
_exit, _Exit -terminate the calling process
SYNOPSIS
#include <unistd. h>
void -exit(int status);
#include <stdlib. h>
void -Exit(int status);
异常退出:
(1)调用abort
(2)当进程收到某些信号时,如ctrl+C
(3)最后一个线程对取消(cancellation)请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态( exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
6、wait、waitpid等待子进程退出
为啥要等待子进程退出:
僵尸进程
父进程等待子进程退出,并收集子进程的退出状态,子进程退出状态不被收集,变成僵死进程(僵尸进程)
使用man查询等待函数:
NAME
wait, waitpid, waitid-wait for process to change state
SYNOPSIS
#include <sys/types. h>
#include <sys/wait. h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait
status:是一个整型数指针,指向终止进程的终止状态,如果不关心终止状态可指定为空指针
WEXITSTATUS(status) 当子进程是正常退出时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。
waitpid
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数:
pid有四种情况:
1.pid==-1 等待任意子进程
2.pid>0 等待进程ID与pid相等的子进程
3.pid==0 等待组ID等于调用进程组ID的任意子进程
4.pid<-1 等待组ID等于pid绝对值的任意子进程
options控制waitpid的操作:
1.WCONTINUED,支持作业控制
2.WUNTRACED,支持作业控制
3.WNOHANG waitpid不阻塞,返回值为0
孤儿进程:
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux避免系统存在过多孤儿进程,会让init进程收留孤儿进程,变成孤儿进程的父进程
wait函数使用实例:
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t retpid;
int i=0;
int status = 10;
retpid = fork();
if(retpid == 0){
while(1){
printf("this is child pid=%d\n",getpid());
sleep(1);
i++;
if(i==3){
exit(5);
}
}
}else{
wait(&status);
while(1){
printf("this is father pid=%d retpid=%d i=%d status = %d\n",getpid(),retpid,i,WEXITSTATUS(status));
//WEXITSTATUS(status)返回status的低八位
sleep(1);
}
}
return 0;
}
运行结果:
this is child pid=2865
this is child pid=2865
this is child pid=2865
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
this is father pid=2864 retpid=2865 i=0 status = 5
^C
7、exec族函数、system()、popen()
这三个函数的功能都是去执行另外一段可执行程序,具体的区别参考:Linux中fork+exec、system和popen的区别.