目录
什么是进程
进程是正在运行的程序的实例。
如何查看进程
- ps命令:ubuntu下, ps -aux|grep +关键字
- top命令
进程标识符
进程控制符(PID),Process Identifier。也常被称为进程标识符。它是各进程的标识,程序一运行系统就会自动分配给进程一个独一无二的PID。进程中止后PID被系统回收,可能会被继续分配给新运行的程序。
系统默认pid:
Pid=0:交换进程(swapper);作用:进程调度。
Pid=1:init进程;作用:系统初始化。
程序中用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符。
C程序的存储空间如何分配
- 正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的指令。
- 初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中出现在任何函数之外的声明:int maxcount = 99;使此变量带有其初值存放在初始化数据段中。
- 非初始化数据段。通常将此段称为bss段,这名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明:long sum [1000] ; 使此变量存放在非初始化数据段中。
- 堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。
- 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。
pid_t fork(void):创建一个新进程。
返回值:
如果成功,在父进程中返回子进程的PID,在子进程中返回0。
如果失败,在父进程中返回-1,并设置errno的值。
pid_t getpid(void):返回当前进程标识符pid。
pid_t getppid(void):返回父进程标识符。
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
pid_t pid_return;
pid_return = fork();//创建一个新进程,返回值大于0的进程为父进程,等于0即处于子进程
if(pid_return > 0){//返回值大于0,处于父进程,返回的是子进程PID
printf("in parent process,pid= %d, pid_return=%d\n",getpid(),pid_return);
}else if(pid_return == 0){//等于0,处于子进程
printf("now in child process,pid = %d, pid_return=%d\n",getpid(),pid_return);
}
return 0;
}
父子进程内存空间:
- 子进程内存空间获得父进程的数据段、堆和栈的副本,代码段是共享父进程的。
- 写时拷贝,当父、子进程中有更改相应段的行为时,再为子进程相应的段分配物理空间,获得父进程相应段副本。
vfork与fork的区别
区别一:
vfork直接使用父进程存储空间,不拷贝。
区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
进程的退出
正常退出
- Main函数调用return。
- 进程调用exit(),标准c库。
- 进程调用_exit()或者_Exit(), 属于系统调用。
- 进程最后一个线程返回。
- 最后一个线程调用pthread_exit。
异常退出
- 调用abort。
- 当进程收到某些信号时,如ctrl+C。
- 最后一个线程对取消(cancellation) 请求做出响应。
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原 三个终止函数(退出、_Exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态 (termination status)。在任意一种情况下,该终止进程的父进程都能用wai 因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
父进程等待子进程退出并收集退出状态
僵尸进程,当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
孤儿进程,如果父进程先退出 ,子进程为孤儿进程,init进程接管子进程,变成孤儿进程的父进程,子进程退出后init会回收其占用的相关资源。
每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程。
调用fork()函数创建子进程后,父子进程同时跑,父进程可以调用wait()函数等待子进程退出后(exit())再继续执行,并且wait()收集子进程的退出状态(正常或异常)。用宏解析状态。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int status = 0;
pid = fork();
if(pid > 0){
wait(&status);//等待子进程退出,并收集退出状态
if(WIFEXITED(status)){//子进程调用exit()正常退出
printf("子进程正常退出,status:%d\n",WEXITSTATUS(status));//返回exit参数的低8位值
}else{
printf("子进程异常退出\n");
if(WIFSIGNALED(status)){//子进程因为信号退出
printf("收到信号:%d\n",WTERMSIG(status));
}
}
while(1){
printf("father process,pid:%d,getpid:%d\n",pid,getpid());
sleep(1);
}
}
if(pid == 0){
int cnt = 20;
while(cnt){
printf("child process,pid:%d,getpid:%d\n",pid,getpid());
sleep(3);
cnt--;
}
exit(1);
}
return 0;
}
pid_t wait(int *status)
- 如果共所有子进程都还在运行,则阻塞。
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
- 如果它没有任何子进程,则立即出错返回。
status参数:是一个整型数指针;非空:子进程退出状态放在它所指向的地址中。空:不关心退出状态。
wait()使调用者阻塞,但waitpid()可以使调用者不阻塞。
pid_t waitpid(pid_t pid, int *status, int options);
- pid参数:
pid == -1 等待任一子进程。就这一方面而言,waitpid与wait等效。
*pid > 0 等待其进程ID与pid相等的子进程。pid == 0 等待其组ID等于调用进程组ID的任一子进程。
pid <-1 等待其组ID等于pid绝对值的任一子进程。
- status参数:
是一个整型数指针;非空:子进程退出状态放在它所指向的地址中。空:不关心退出状态。
- options参数:
WCONTINUED:若实现支持作业控制。那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。
*WNOHANG:若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0。
WUNTRACED:若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态、并且其状态自暂停以来还未报告过、则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。
exec族函数
参考博文:linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)_牛仔的blog-CSDN博客_execle
exec族函数的作用:
在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe。
区别:
l :使用参数列表。指定可执行文件的路径,每个命令行参数都说明为一个单独的参数,并以NULL结束。
p:可以不指定可执行文件的路径,直接使用文件名,并从PATH环境变量中进行寻找该可执行文件。
v:先构造一个指向各参数的指针数组,然后将该数组的地址作为函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arr[] = {"ls","-l",NULL};
printf("before exec\n");
//int execl(const char *path, const char *arg, .../* (char *) NULL */);
/*if(execl("/bin/ls","ls","-l",NULL) == -1){
perror("exec");
}*/
/*if(execlp("ls","ls","-l",NULL) == -1){
perror("exec");
}*/
if(execvp("ls",arr) == -1){
perror("exec");
}
printf("after exec\n");
return 0;
}
system()函数
参考博文:linux system函数详解 - 南哥的天下 - 博客园
与exec族函数区别,system函数创建一个子进程执行程序并且等待子进程结束,所以不会取代所在的进程。
#include <stdio.h>
#include <stdlib.h>
int main()
{
system("ls -l");
printf("after sysytem\n");
return 0;
}
popen()函数
执行某个程序,并且将程序运行产生的数据放到“管道”中。使用fread()函数就可以读取管道的内容,这样就可以打印出程序运行的结果。
#include <stdio.h>
int main()
{
FILE *fd;
char ret[1024]={"\0"};
//FILE *popen(const char *command, const char *type);
fd = popen("ls","r");
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
int n = fread(ret,1,1024,fd);
printf("read %d byte,ret=\n %s",n,ret);
return 0;
}