一、进程
1、什么是进程?
进程是程序的一次执行,进程为应用程序的运行实例,是应用程序的一次动态执行。看似高深,我们可以简单地理解为:它是操作系统当前运行的执行程序。进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。进程包括程序和数据两部分。进程是可以与其他程序并发执行的程序的一次执行,是系统进行资源分配和调度的一个独立单位。
2、如何查看系统中有哪些进程?
2.1、使用ps-aux指令查看
以上进程指令截取部分,那么如何在这么多的进程中找到我要找的那一个。这里需要配合使用 grep来查找进程中的某一指令。
2.2、使用top指令查看当前所有的进程,类似于window任务管理器。
linux系统下使用top指令:
window任务管理器运行界面:
无论是top指令还是window任务管理器都会实时显示CPU和内存的占有率。
3、什么是进程标识符?
系统给每个进程定义了一个唯一标识该进程的非负整数叫做pid
pid = 0;称为(swapper)交换进程,作用:进程调度。
pid= 1;称为init进程,作用:系统初始化。
4、什么是父进程、子进程?
在Linux里,除了进程0以外的所有进程都是由其他进程使用系统调用fork()函数创建的,这里调用fork()函数创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
程序演示如下:
一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这是请求到达时,父进程调用fork(),使子进程处理此请求。父进程则继续等待下一个服务请求到达。
一个进程要执行一个不同的程序。这对 shell 是很常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
注:上述代码会一直检测用户输入,如果data=1,代表有新的客户端接入,创建一个新的子进程,并且各个子进程与父进程之间不受干扰。
5、c程序的存储空间怎么分配
C语言中编译后,主要把内存和ROM分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区。
<1>栈区:由编译器自动分配释放,存放局部变量、形参、返回值。
<2>由程序员分配内存和是释放。例如调用函数:malloc()和free()。
<3>全局静态区:未初始化全局静态区(.bass)、已初始化全局静态区(.data)。
<4>常量区:例如字符串abcd。
<5>代码区:存放程序的代码。
二、创建进程
在linux中fork函数是非常重要的系统调用接口,从已经存在的进程中创建一个新的进程,新进程为子进程,原进程为父进程。
1、使用fork函数创建一个进程 pid_t fork(void);
fork函数调用成功,返回两次。返回值为0,代表当前进程是子进程;返回值为非负数,代表当前
进程为父进程。代码如下:
2、通过fork返回值判断进程是否为父进程还是子进程。
如果不直接判断pid是否大于0或者等于0,而是通过访问fork函数的返回值来判断是否进入父进程还是子进程,如上所述由fork创建的新进程被称为子进程,fork函数被调用一次,但返回两次,两次返回的唯一区别是,子进程的返回值是0,父进程的返回值是新子进程的进程id。代码如下:
3、fork与vfork的区别:
3.1、vfork直接使用父进程的存储空间,不拷贝。
fork函数如下:此时子进程和父进程会相互争夺内存资源。
3.2、vfork保证子进程先执行,当子进程调用exit退出后,父进程才执行。
三、进程退出:
1、正常退出
main函数调用return
进程调用exit(),标准c库
进程调用_exit()或者_Exit(),属于系统调用
进程最后一个线程返回
最后一个线程调用pthread_exit
对应头文件如下:
2、异常退出:
调用abor
当进程收到某些信号时,ctrl+c
最后一个线程对取消请求做出响应
3、父进程等待子进程退出
3.1、为什么要等待子进程退出
代码演示如下:父进程会等待子进程运行3次才开始执行。
3.2、子进程退出状态不被收集,变成僵尸进程。
3.3、孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程;
Linux避免系统存在过多的孤儿进程,init进程会收留孤儿进程,变成孤儿进程的父进程。init进程收留孤儿进程,代码如下:
3.4、父进程等待子进程退出,并收集子进程的退出状态
过程:收集子进程的退出状态,就需要将exit( ),里面的状态码给wait(* status) 里的*status;再由返回终止状态的宏来解析状态码,最终完成对子进程的退出状态码收集。其中status是一个整型数指针。非空,子进程退出状态放在它所指向的地址中,若为空,不关心退出状态。
3.5、wait和waitpid
wait函数简单介绍:
如果其所有子进程都还在运行,则阻塞;
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
如果它没有任何子进程,则立即出错返回。
waitpid函数中pid参数解释如下:
pid==-1 | 等待任一子进程。 就这方面而言, waitpid与wait等效。 |
pid>0 | 等待其进程ID与pid相等的子进程。 |
pid==0 | 等待其组ID等于调用进程组ID的任一子进程。 |
pid<-1 | 等待其组ID等于pid绝对值的任一子进程。 |
waitpid的options常量:
WCONTINUED | 若实现支持作业控制。那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。 |
WNOHANG | 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0。 |
WUNTRACED | 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过。则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程。 |
代码如下:
四。函数介绍:
1、exec族函数
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
1.1、exec函数
exec函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
如上图 :./yang为可执行路径名,yang是可执行文件名,123是参数列表,必须以NULL结尾。yang.c编译后,生成可执行文件yang在当前路径目录下,然后再编译execl.c并执行execl可执行文件。用execl 找到并执行yang。
同理如果想要执行ls -l指令查看文件权限,可以先找到ls的路径,再赋参数-l给ls,如下图:
1.2、 execlp函数
带p的一类exac函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。所以execlp函数能通过环境变量PATH查找到可执行文件p
1.3、 execvp函数
带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
如char *arg[]这种形式,且arg最后一个元素必须是NULL,例如char *arg[] = {“ps”,“NULL”,NULL};
1.4、 execv函数
execvp不需要加绝对路径,execv需要加绝对路径
2、system函数
返回值:如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。
函数原型: #include <stdlib.h>
int system(const char *command);
代码演示如下:
3、popen函数
popem 函数比system函数的好处是可以获取运行结果
函数原型如下:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
参数说明:
commmand:是一个指向以NULL结束的shell命令字符串的指针,这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
mode:只能是读或者写的一种,得到的返回值(标准i/o流),也具有和type相对应的只读或者只写类型,如果type是“r”,则文件指针连接到command的标准输出;如果type是“w”,则文件指针连接到command的标准输出。
返回值:如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL。
代码演示如下: