Linux系统环境编程之进程1
1.引言
问1. 什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c –o pro 磁盘中生成pro文件,叫做程序。
进程是程序的一次运行活动, 通俗点意思是程序跑起来了,系统中就多了一个进程。
问2. 如何查看系统中有哪些进程?
a.使用ps指令查看 实际工作中,配合grep来查找程序中是否存在某一个进程。ps -aux | grep
b.使用top指令查看,类似windows任务管理器
问3. 什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID, 叫做pid,类似身份证。
Pid=0: 称为交换进程(swapper) 作用—进程调度
Pid=1:init进程 作用—系统初始化
编程调用getpid函数获取自身的进程标识符 getppid获取父进程的进程标识符。
问4. 什么叫父进程,什么叫子进程?
进程A创建了进程B 那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
问5. C程序的存储空间是如何分配?
2.进程创建实战
使用fork函数创建一个进程 pid_t fork(void);
fork函数调用成功,返回两次 返回值为0,代表当前进程是子进程 返回值非负数,代表当前进程为父进程 调用失败,返回-1
fork创建一个子进程的一般目的:
(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的一父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork使子进程处理此请求。父进程则继续等待下一个服务请求到达。
(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec(我们将在8.10节说明exec)。
fork编程实战:
一个现有进程可以调用fork函数创建-个新进程。
#include <unistd.h>
pid_ t fork (void) ;
返回值:子进程中返回0,父进程中返回子进程ID.出错返回-1
由fork创建的新进程被称为子进程(child process)。fork函 数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使 子进程得到返回值0的理由是:一 个进程只会有个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID (进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段(见7.6节)。
由于在fork,之后经常跟险着exec,所以以在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy On Write, COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一 个试图修改这些区域,则内核只为修改区域的那块内存制作一一个副本, 通常是虚拟存储器系统中的一页 。Bach[ 19861)的9.2节和McKusick等1996的5 6节和5.7节对这种特征做了更详细的说明。
进程
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid;
pid_t pid1;
pid = getpid();
printf("before fork pid is : %d\n",pid);
fork();
pid1 = getpid();
printf("after fork pid is : %d\n",pid1);
return 0;
}
进一步创建进程获取fork的返回值
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid;
pid_t pid1;
pid = getpid();
printf("before fork pid is : %d\n",pid);
pid_t retpid = fork();
pid1 = getpid();
printf("after fork pid is : %d\n",pid1);
if(retpid > 0){
printf("this is father pid , father fork : %d\n",retpid);
}
else if(retpid == 0){
printf("this is child pid, child fork : %d \n",retpid);
}
return 0;
}
vfork函数 也可以创建进程,与fork有什么区别?
关键区别一: vfork 直接使用父进程存储空间,不拷贝。
关键区别二: vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
//进程,vfork与fork的区别
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
int cnt = 0;
pid_t retpid = vfork();
if(retpid > 0){
while(1){
printf("this is father pid , father fork : %d\n",retpid);
sleep(1);
}
}
else if(retpid == 0){
while(1){
printf("this is child pid, child fork : %d \n",retpid);
sleep(1);
}
}
return 0;
}
进程退出
正常退出
1.Main函数调用return
2.进程调用exit(),标准c库
3.进程调用_exit()或者_Exit(),属于系统调用
补充:
1.进程最后一个线程返回
2。最后一个线程调用pthread_exit
异常退出
1.调用abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation)请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形、我们都希望终u止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_ exit和_ Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。 在任心一种情况下,该终止进程的父进程都能用wait或waitpid函数(在下一节说明)取得其终止状态。
//退出子进程
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int cnt = 0;
pid_t retpid = fork();
if(retpid > 0){
while(1){
printf("cnt : %d",cnt);
printf("this is father pid , father fork : %d\n",retpid);
sleep(1);
}
}
else if(retpid == 0){
while(1){
printf("this is child pid, child fork : %d \n",retpid);
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
//退出子进程,并获取子进程退出编号
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int cnt = 0;
int status = 0;
pid_t retpid = vfork();
if(retpid > 0){
wait(&status);
while(1){
printf("father wait child end! status is : %d\n",WEXITSTATUS(status));
printf("cnt : %d\n",cnt);
printf("this is father pid , father fork : %d\n",retpid);
sleep(1);
}
}
else if(retpid == 0){
while(1){
printf("this is child pid, child fork : %d \n",retpid);
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
为啥要等待子进程退出?
父进程等待子进程退出 并收集子进程的退出状态。
子进程退出状态不被收集,变成僵死进程(僵尸进程)
区别: waip使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
waitpid函数:
status参数: 是一个整型数指针 非空: 子进程退出状态放在它所指向的地址中。 空: 不关心退出状态。
//退出子进程,并获取子进程退出编号
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int cnt = 0;
int status = 0;
pid_t retpid = vfork();
if(retpid > 0){
wait(&status);
while(1){
printf("father wait child end! status is : %d\n",WEXITSTATUS(status));
printf("cnt : %d\n",cnt);
printf("this is father pid , father fork : %d\n",retpid);
sleep(1);
}
}
else if(retpid == 0){
while(1){
printf("this is child pid, child fork : %d \n",retpid);
sleep(1);
cnt++;
if(cnt == 3){
exit(2);
}
}
}
return 0;
}
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程 Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
//孤儿子进程
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int cnt = 0;
int status = 0;
pid_t retpid = fork();
if(retpid > 0){
printf("this is father pid , father fork : %d\n",retpid);
}
else if(retpid == 0){
while(1){
printf("this is child pid, child fork : %d \n",retpid);
printf("this is a guer,father fork is : %d\n",getppid());
sleep(1);
cnt++;
if(cnt == 3){
exit(2);
}
}
}
return 0;
}