一. 程序
程序 (program)是什么?
计算机程序 (computer program)一般是指以某些程序设计语言编程,能够运行于某种目标体系结构上程序 = 数据结构 + 算法
数据结构:用来表示人们思维对象的抽象概念的物理表现叫做数据 (对问题中抽象出来的实体)
对数据的处理规则叫做指令
算法:解决问题完整而且准确的描述,是一系列解决问题的清晰的指令
计算机程序就是算法和数据的集合 (算法操作数据)
一个程序的执行过程就是一个计算
程序是一个静态的概念
二. 程序的执行方式
(1) 顺序执行
一个程序完全执行完毕后,另一个程序才能被执行
缺点:CPU的利用率非常低
某些程序在等待外部条件的时候,CPU是空闲的
输入数据----->计算------>输出
(2) 并发执行
多个程序同时运行 (宏观),本质上还是顺序执行
一个程序有非常多的指令
把一条指令的执行过程,分为几个不同的步骤:
取指令----->执行------>回写
不同的步骤,由不同的硬件去完成
理论上来说,就可以多个程序同时运行 (单cpu,宏观)
为了提高cpu的利用率,增加吞吐量,引入"并发执行"
现代的操作系统为了能够让程序并发执行,特地引入"进程"的概念
正在进行的程序
三. 进程
进程是什么?
进程是具有独立功能的程序关于某一个数据集合上的一次运行活动
理解为"炒菜 (进程)和菜谱 (程序)"的关系
test.c -------->源程序 (源代码)
int main() {
int a, b;
int sum;
scanf("%d%d", &a, &b);
sum = a + b;
printf("sum = %d\n", sum);
return 0;
}
gcc test.c -o test ===> test (程序,二进制文件)
./test // 产生一个进程
./a.out & 进程在后台运行 (不会影响前台)
前台:shell终端就是前台,在shell终端上运行的进程 (会影响终端),可以称之为前台进程/交互进程
在linux中是如何描述进程的—进程控制块 (PCB):有一个进程就会对应唯一的进程控制块struct task_strcut 文件系统 fs *root 用户 *pwd 工作目录 进程文件表项 files 进程打开的文件 进程地址空间 mm 进程号 pid 标识一个进程 进程状态 运行、休眠... 优先级 进程的调度方式 信号处理方式 signal ...
在Linux中是如何管理进程控制块的:
1. 链表:方便进程控制块的插入和删除
ps aux 列举所有进程的信息
ps aux | grep a.out
S+:S sleep ---> 阻塞态 + ---> 前台
R:running ---> 运行态
2. 树:方便找出谁创建了这个进程,方便找出某个进程的父进程和子进程
pstree
3. 哈希表 (数组):方便查找特定的进程 pid
kill -9 pid 杀死进程号为pid的进程
kill:发送一个信号 -9:信号值 pid:进程号
在 /proc 目录下 以文件的方式列举出了所有进程信息 ls /proc
四. 进程和程序的区别
(1) 程序是静态概念 (是指令和数据的集合)
进程是动态的概念 (动态产生,动态消亡)
(2) 进程是一个程序的一次执行活动,一个程序可以对应多个进程 (多进程编程,多个进程的代码段是一样的,共享代码段的空间 (指令))
(3) 进程是一个独立的活动单位,进程是竞争系统资源的基本单位 (内存, cpu时间....)
OS为什么要引入进程呢?就是为了能够让程序并发运行(同一时间段有多个程序正在运行----宏观)
程序的并发,实际上就是进程的并发
并发:在计算机科学领域中,是指同时执行多个独立的任务或者操作的能力,描述的是不同的任务交替 (时间短暂)获得CPU,从而达到同时执行的目的 (宏观),微观上并发仍然是顺序执行并行:描述的是不同的任务同时使用CPU,微观、宏观统一,真正意义上的同时执行
五. 进程状态
OS把一个进程的执行过程,分为三个不同的阶段 (状态):
就绪态 (Ready): 进程所有的准备工作已经完成,只需要CPU去执行进程的指令
运行态 (Running): CPU正在执行这个进程的指令
阻塞 (等待) 态 (Blocking,Waiting): 进程正在等待其他的外部事件 (如:输入......)
停止态 (Stop): Ctrl + z 或者收到 STOP这个信号,进程会由 运行态---->停止态,输入fg,会由 停止态 --->就绪态
僵尸态 (Zombie): 进程结束之后,短暂的一个状态,主要是让父进程收尸 (知道死因)
进程的这些状态可以进行切换
僵尸进程 (Zombie):
当进程退出时,父进程没有读取到子进程的退出码,子进程就会成为僵尸进程
一个进程结束了,但是它的父进程没有等待 (wait / waitpid)它,那么它就会变成僵尸进程
僵尸进程会以终止状态保持在进程表中,一直等待父进程读取退出状态码
危害:占用系统资源
孤儿进程:
父进程结束了,子进程就被称为"孤儿进程"
孤儿进程被 init 系统进程收养
"就绪队列":Ready Queue
所有处于就绪态的进程,都处于一个 "队列" 中
"调度程序":负责确定下一个进入"Running"状态的进程
确定下一个占用CPU的进程
"调度策略":调度算法
分时系统:调度策略以"时间片轮转"为主要策略的系统
"时间片轮转":分时,每一个进程执行一段时间 (时间片)
如:大部分的桌面操作系统,linux android windows macos unix......
实时系统:调度策略以"实时策略"为主要策略的系统"实时策略":每一次调度都取优先级最高的那个进程执行,直到这个进程执行完毕或者它主动放弃CPU或者其他更高优先级的进程抢占
如:ucos,freeRTOS...可抢占和不可抢占
六. linux进程地址空间布局
操作系统使得每个进程认为 它拥有所有资源 -------进程地址空间
程序运行的第一件事,就是申请一块内存区域来存储程序的"数据 (用户数据,指令)",不同的数据属性是不一样的,进程地址空间是进行分段管理 / 存储的"分区域" 来存储程序的数据
"分段":分不同的逻辑区域
不同属性的数据,存储到不同的"内存段"中,不同的内存段的属性以及管理方法不同
.text 主要存放指令
只读并且共享,这段内存在程序运行期间内不会被释放
"指令段":随进程的持续性
.data 数据段
主要存放程序已经初始化的全局变量和已经初始化的静态 (static)变量
已经初始化的静态 (static)变量和已经初始化的全局变量一样,程序运行就立刻申请空间
可读可写,这段内存在程序运行期间内不会被释放,随进程的持续性
.bss 数据段
主要存放程序未初始化的全局变量和未初始化的静态 (static)变量,如果没有使用该变量,就不会申请空间,未初始化的全局变量和未初始化的静态 (static)变量暂时不需要空间
可读可写,这段内存在程序运行期间内不会被释放,随进程的持续性
.bss段,在程序初始化时,如果用到了这些变量可能全部被初始化为0 (未初始化的全局变量和未初始化的静态 (static)变量自动初始化为0)
.rodata 只读数据段
主要存储程序中的只读数据 (如:字符串常量)
只读 (不可以更改),这段内存在程序运行期间内不会被释放,随进程的持续性
栈 (stack)空间
主要存放局部变量 (非static的局部变量)
可读可写,这段空间,会自动释放 (代码块执行完了,代码块中的局部变量的空间就会自动释放),随代码块的持续性
返回一个局部变量的地址,就会有问题
堆 (heap)空间:动态内存空间主要是malloc / realloc / colloc动态分配的空间
可读可写的,这段内存在程序运行期间,一旦分配就会一直存在,直到你手动free或者程序结束
防止''内存泄漏'' / "垃圾内存"
%p打印的是虚拟内存地址#include <stdio.h> int main() { static int a; printf("%p\n", &a); while (1); return 0; } gcc 1.c ./a.out & ./a.out & ./a.out & 三次打印的地址有没有可能相同? 有可能,原因:虚拟内存地址
例子:
#include <stdio.h> #include <stdlib.h> int a = 5; // .data int b; // .bss static int k = 6; // .data // f函数有问题,不能返回一个局部变量的地址 int* f() { int c = 5; // 栈空间 return &c; // 返回一个局部变量的地址,c在返回后,就被系统回收了 } // f1函数有问题,每调用一次,就会造成1024个字节的垃圾内存 void f1() { // p1是局部变量,但是p1指向的空间是堆空间 char *p1 = malloc(1024); } // f2函数正确 char *f2() { char *p = "hello"; return p; // 址传递,返回的是p所指向的地址 } int main() { int *p = &a; // p在栈空间 char *p1 = "12345"; // p1在栈空间 // "12345"在.rodata // *(p1 + 1) = 'B'; // 段错误,通过指针p1去修改只读内存 // p1[1] = 'B'; // 同上 *(p1 + 1) <=====>p1[1] p1 = "ABC"; // 没问题,p1在栈空间,可读可写 p1 = f2(); // p1指向"hello" char *p2 = malloc(1024); // p2在栈空间,1024个字节在堆空间 *p2 = 'A'; // 没问题, 堆空间是可读可写的 p2 = "nihao"; // 语法没问题,但是会造成内存泄漏,malloc开辟的1024个字节的空间找不到了 char x[] = {"abcde"}; // x的空间在栈中,"abcde"在.rodata中,只是在x的空间中存在一份copy x[1] = 'B'; // 没问题 // x = "xxxxx" // 有问题,数组名是一个指针常量,是一个指针类型的常量,不能修改 return 0; }
七. linux下关于进程的API函数解析
(1) 创建一个新进程 fork
系统在实现fork时,有一个特点:写时复制 (copy-on-write)
fork() 是用来创建一个新进程的,你要使用fork创建一个新进程,首先你要知道一个已有的进程里面包含什么东西?----------> 数据 (系统数据和用户数据) 和 指令
所以你创建一个新进程,也需要数据和指令,来自哪里呢?
来源于父进程 (调用fork函数的进程,我们称之为父进程,新创建的进程称之为子进程),子进程的数据和指令都来自于父进程
fork这个函数在创建子进程的时候:
copy (克隆clone) 了父进程的数据和指令 !!!
数据:用户数据和系统数据
父进程的变量,数据对象
标准IO的缓冲区
文件描述符
文件偏移量
......
======>
fork 成功的时候,就会有两个一模一样的进程在执行一模一样的代码
既然两个进程是一模一样的,如何区分父子进程呢?
fork() 函数调用一次,有两次返回,在fork函数内部实现的。如果成功后,就会有两个进程在执行当前的代码,为了区分父子进程,所以fork一次调用,两次返回,一次是父进程返回,还有一次是子进程返回
fork的伪代码可能是这样实现的:
fork() { ...... clone(克隆); ...... // 下面的代码有两个进程在执行 if (是父进程) { return 子进程的pid; } else if (是子进程){ return 0; } }
函数原型:
NAME fork - create a child process // 创建一个子进程 SYNOPSIS #include <unistd.h> pid_t fork(void); 返回值: 如果失败返回-1,同时errno被设置 如果成功,有两个返回值(同一段代码有两个进程在运行) 父进程返回子进程的进程号(pid) 子进程返回0
fork之后,操作系统会copy出一个与父进程完全相同的子进程 (申请一个进程控制块用于描述子进程),这两个进程共享代码段的空间 (指令),但是数据段是相互独立的,子进程的数据是从父进程地址空间中copy过来的,指令指针 (PC)也完全相同 (copy后,父进程运行到哪里,子进程也会运行到哪里)
问题:
fork 函数后面的语句,有两个进程正在执行,那么谁先执行 (单CPU)呢?
不一定,这个决定于进程调度算法
copy完成之后,子进程就独立了 (有自己的pid),子代的父进程 ID与父代的进程 ID 相同
linux系统会为每一个进程,分配一个唯一的ID (>0的整数),使用类型pid_t (unsigned int)表示还有两个配套的函数:用于获取进程自己的 ID 和父进程的 ID
NAME getpid,getppid - get process identification SYNOPSIS #include <sys/types.h> #include <unistd.h> pid_t getpid(void); // 用于获取进程本身的 ID pid_t getppid(void); // 用于获取调用函数进程的父进程的 ID
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { int a = 1, b = 2; printf("hello world\n"); // 打印一次 pid_t pid = fork(); // 创建子进程,下面的代码就有两个进程在运行 if (pid == -1) { perror("fork failed"); return -1; } else if (pid > 0) { // 父进程,返回子进程的pid printf("I am father, a = %d, b = %d\n", a, b); printf("my son pid is %u\n", pid); } else if (pid == 0) { // 子进程,返回0 printf("I am son, a = %d, b = %d\n", a, b); printf("my pid is %u\n", getpid()); printf("my father pid is %u\n", getppid()); } printf("haha nihao\n"); // 打印两次 return 0; } hello world I am father, a = 1, b = 2 my son pid is 41938 haha nihao I am son, a = 1, b = 2 my pid is 41938 my father pid is 41937 haha nihao
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { int a = 1, b = 2; printf("hello world"); // 打印两次,仅仅是打印到缓冲区 // 缓冲区的数据会被copy到子进程 pid_t pid = fork(); // 创建子进程,下面的代码就有两个进程在运行 if (pid == -1) { perror("fork failed"); return -1; } else if (pid > 0) { // 父进程,返回子进程的pid printf("I am father, a = %d, b = %d\n", a, b); printf("my son pid is %u\n", pid); // while (1); } else if (pid == 0) { // 子进程,返回0 printf("I am son, a = %d, b = %d\n", a, b); printf("my pid is %u\n", getpid()); printf("my father pid is %u\n", getppid()); // while (1); } printf("haha nihao\n"); // 打印两次 return 0; } hello worldI am father, a = 1, b = 2 my son pid is 42046 haha nihao hello worldI am son, a = 1, b = 2 my pid is 42046 my father pid is 1656 haha nihao
(2) 进程退出
进程退出有两种情况:
一是自杀: 进程指令执行完毕或者自己终止了
a. main函数执行完毕了
main函数退出,表明整个进程执行完毕
b. 在进程执行的任意时刻调用
exit() / _exit()
NAME exit - cause normal process termination // 导致正常进程终止 SYNOPSIS #include <stdlib.h> void exit(int status); status:退出码,表示程序的退出状态,一般由程序员自己设定,返回给父进程的退出信息 正常退出,做一些清理工作(如:把缓冲区的东西,同步到文件中去)
NAME _exit - terminate the calling process SYNOPSIS #include <unistd.h> void _exit(int status); status:退出码,表示程序的退出状态,一般由程序员自己设定,返回给父进程的退出信息 和 exit 的区别是,直接终止进程,不会做清理工作
return 和 exit / _exit 的区别?
return 是函数返回,如果返回的是主函数,则会退出程序
exit 是系统调用,在调用的地方强行退出程序,一旦运行进程就结束了
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void f() { exit(1); // 同步缓冲区的内容 // _exit(1); // 不会同步缓冲区的内容 } int main() { printf("hello"); // 输出到标准IO的缓冲区 f(); printf("nihao\n"); return 0; }
二是他杀:
被操作系统干掉了 (内存的非法访问)
被其他进程终止了 (进程间通信)
父进程可以通过调用wait / waitpid 函数得到子进程的退出码
(3) 等待子进程退出
NAME wait, waitpid, waitid - wait for process to change state SYNOPSIS #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *wstatus); wstatus:用来保存被收集的进程的退出状态的(exit的退出码),是一个int类型的指 针,如果我们对子进程的退出码没有任何兴趣,只想回收资源,避免子进程变为 僵尸进程,可以把wstatus设置为NULL pid_t waitpid(pid_t pid, int *wstatus, int options); 这两个函数的作用是用来等待某(些)一个子进程的状态发生改变的,等待的状态有三种: 1. 子进程退出:main函数返回 / exit / _exit 2. 子进程被信号终止 3. 子进程被信号唤醒 进程一旦调用了wait,就立即阻塞自己,由wait自动分析当前进程的某个子进程是否退出。 如果找到这样一个子进程(僵尸进程),wait就会收集这个子进程的信息, 并且回收子进程的资源,如果没有子进程退出,则一直阻塞自己, 直到出现一个子进程的状态改变 父进程调用wait可以读取子进程的退出状态并且释放子进程的资源,假如没有调用wait, 那么子进程退出后,就会变成僵尸进程(zombie)
wait用来等待任意一个子进程的状态改变 pid_t wait(int *wstatus); wstatus:用来保存子进程的退出信息(子进程的退出码) 返回值: 成功返回退出的那个子进程的ID 失败返回-1,同时errno被设置 wstatus保存了子进程的退出状态信息,这些状态信息都保存在一个整数中(status指向的内 存),可以使用一些宏解析那个整数: 常见的有: WIFEXITED(wstatus):wait if exited(exit,_exit) 这个宏用来指出子进程是否为正常退出的,如果是正常退出的(exit,_exit,主函 数中的return),宏会返回一个非0值 WEXITSTATUS(wstatus):wait exit status 当WIFEXIFED(wstatus)返回非0时,我们可以使用这个宏去提取子进程的返回值 (子进程的退出码,如:子进程exit(5)退出,WIFEXITED(wstatus)就会返回5),如果进程 不是正常退出的(如:他杀),WIFEXIFED(wstatus)返回0时,这个值就没有任何意义 利用wstatus中的某一些位去存储一些固定的信息 退出码:unsigned char WIFSIGNALED(wstatus):wait if signaled 如果进程是被信号终止的,这个宏就为真
waitpid是用来等待子进程状态改变的,只不过可以指定等待的子进程的进程号 pid_t waitpid(pid_t pid, int *wstatus, int options); pid:指定要等待的进程或者进程组 pid == -1 表示等待任意子进程 pid == 0 表示等待与调用进程同组的任意子进程 "进程组":就是一组进程,默认每个进程都有属于自己的一个进程组(进程属性), 每一个进程组都有一个组长,创建这个进程组的进程就是组长进程, 进程组有一个组id,这个组id就是组长进程的id pid < -1 表示等待组id等于指定的pid绝对值的那个组的任意子进程(等待指定组中的 一个子进程) pid > 0 表示等待指定的子进程(进程号为pid) wstatus:同wait,保存子进程的退出码(指针,指向一个可用的内存) options:选项 0:表示堵塞等待 WNOHANG:非阻塞等待,假设调用waitpid时没有子进程退出,立即返回 返回值: 成功返回退出的那个进程的进程id 失败返回-1,同时error被设置 wait(&wstatus) <======> waitpid(-1, &wstatus, 0)
(4) exec函数族
exec函数说明:
fork是创建一个子进程,创建的子进程是父进程的副本,但是我们有时候想要让子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序的方法
它可以根据指定的文件名找到可执行文件,并且使用它的数据段和指令段取代调用者进程的数据段和指令段。在执行完后,原调用者进程除了进程号以外,其他的全部都被替换为可执行程序的数据了
在linux中使用exec函数族主要有以下两种情况:a. 当进程认为自己不能再为系统和用户做出任何贡献的时候,就可以调用exec函数让自己 "重生"
b. 如果一个进程想要执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用exec函数让子进程去执行另一个程序
exec函数就是让进程去执行指定的程序文件 你必须指定需要执行的程序的名字? 一个在文件系统中的程序的文件名(绝对路径 / 相对路径) 让另一个程序文件的数据和指令覆盖当前进程的数据和指令 你指定的程序可能还需要参数,你必须指定程序文件的参数,在linux中,参数都是字符串 所以指定程序的参数有两种方式: l(exec中带l的版本):list 把程序运行的参数,一个一个的列举出来,程序的第一个参数就是程序的名字(不需要 带路径的) "sum_test", "123", "456", NULL v(exec中带v的版本):vector 向量,数组 把程序的运行参数,做成一个char*的数组(指针数组) char *argv[] = {"sum_test", "123", "456", NULL}; #include <stdio.h> int my_atoi(char *str) { int d = 0; while (*str >= '0' && *str <= '9') { d = d * 10 + (*str - '0'); str++; } return d; } int main(int argc, char *argv[]) { int a = my_atoi(argv[1]); int b = my_atoi(argv[2]); printf("sum = %d\n", a + b); return 0; }
NAME execl, execlp, execle, execv, execvp, execvpe - execute a file // 执行一个二进制文件 SYNOPSIS #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ... /* (char*) NULL */); // 把需要指向的程序通过list的方式指定 path:指向要执行的那个程序文件的文件名(一般带路径) 从第二个参数开始,就是执行该程序文件需要的命令行参数(要包括程序名, 程序名以不带路径的方式作为第一个参数),以NULL结尾 返回值: 失败返回-1,同时errno被设置 成功,就永远不会返回了!!! 因为进程的整个指令和数据段都被替换掉了 如:execl("/home/gec/sum_test", "sum_test", "250", "251", NULL); -------------------------------------------------------------------------- // exec中带p的版本:p(PATH) 系统中有一个环境变量,PATH 环境变量是什么东西? 就是整个系统环境(所有进程都共享)里面的变量 echo $PATH PATH=dir1:dir2:dir3:......:dirN 指定sh中程序文件的搜索路径,意思就是你运行一个程序时,不指定路径,系统就会到 这些目录中去找,如果找到了就执行程序,如果没找到就报错 ------> 如果你要运行的程序文件本身就在标准的搜索路径(PATH)下,就不需要指定路径了 export可以导出一个临时的环境变量 export PATH=$PATH:... int execlp(const char *file, const char *arg, ... /* (char*) NULL */); file:你要执行的那个程序的名字(不需要带路径,因为已经在标准路径下面了), 其他参数和execl一样 ----------------------------------------------------------------------------- // v:vector 数组 使用数组的方式指定要执行的程序的参数 // char *argv[] = {"sum_test", "123", "456", NULL}; int execv(const char *path, char *const argv[]); path:指定要执行的那个程序文件的文件名(一般带路径) char *const argv[]:指针数组,本质是数组,里面的每一个元素是指针 argv:指针数组,里面的每一个元素都是指针,指向程序需要的参数。执行该程序文件需 要的命令行参数(要包括程序名,程序名以不带路径的方式作为第一个成员),以NULL结尾 返回值: 失败返回-1,同时errno被设置 成功,就永远不会返回了!!! 因为进程的整个指令和数据段都被替换掉了 ----------------------------------------------------------------------------- int execvp(const char *file, char *const argv[]); file:你要执行的那个程序的名字(不需要带路径,因为已经在标准路径下面了), 其他参数和execl一样 char *argv[] = {"ps", "-aux", NULL}; execvp("ps", argv); ----------------------------------------------------------------------------- exec函数族使用了系统默认的环境变量,也可以传入指定的环境变量(必须在标准的 搜索路径(PATH)下可以查找到),这里的"e"(environment)结尾的两个execle / execvpe 就可以在envp中指定当前进程所使用的环境变量,也可以在标准的搜索路径(PATH)下查找 int execle(const char *path, const char *arg, ... /*, (char*) NULL, char *const envp[] */); 使用execvpe需要在头文件之前定义这个宏:#define _GNU_SOURCE int execvpe(const char *file, char *const argv[], char *const envp[]); execlp / execvpe的其他参数都和以前的一样 envp:新指定的环境变量,可以同时指定多个环境变量,以NULL结尾 例子: char *envp[] = {"PATH=/bin:/usr/bin", NULL}; // path:相对路径 or 绝对路径 execle("/bin/ps", "ps", "-aux", NULL, envp); char *argv[] = {"ps", "-aux", NULL}; char *envp[] = {"PATH=/bin:/usr/bin", NULL}; // file:可以不带路径 execvpe("ps", argv, envp);
#include <stdio.h> // 把指定的数字字符串转换为数字 int my_atoi(char *str) { int d = 0; while (*str >='0' && *str <= '9') { d = 10 * d + (*str - '0'); str++; } return d; } int main(int argc,char *argv[]) { printf("i am sum_test\n"); // 计算两个整数的和,加数都通过命令行参数指定 int sum; int a, b; a = my_atoi(argv[1]); b = my_atoi(argv[2]); sum = a + b; printf("sum = %d\n", sum); return 250; }
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> int main() { printf("hello\n"); pid_t pid = fork(); // 创建子进程 if (pid == -1) { perror("fork error"); return -1; } else if (pid > 0) { // 父进程 printf("i am father,my pid is %d,my son is %d\n", getpid(), pid); // 子进程结束,父进程没有读取子进程的退出码且回收资源,子进程变成僵尸进程 int status; // 保存子进程的退出码 pid_t p = wait(&status); // 阻塞自己 // printf("status = %d\n", status); // 没有任何意义 if (WIFEXITED(status)) { // 子进程是否是正常退出 printf("son terminated normally\n"); printf("son exit code: %d\n", WEXITSTATUS(status)); } else { printf("son terminated not normally\n"); } if (WIFSIGNALED(status)) { printf("son is kill by signal\n"); } } else if (pid == 0) { // 子进程 printf("i am son,my pid is %d,my father is %d\n", getpid(), getppid()); // execl("/home/sc/sum_test", "sum_test", "250", "251", NULL); // char *argv[] = {"ps", "-aux", NULL}; // execv("/bin/ps", argv); char *envp[] = {"PATH=/bin:/usr/bin", NULL}; // path:相对路径 or 绝对路径 execle("/bin/ps", "ps", "-aux", NULL, envp); // char *argv[] = {"ps", "-aux", NULL}; // char *envp[] = {"PATH=/bin:/usr/bin", NULL}; // file:可以不带路径 // execvpe("ps", argv, envp); } return 0; }
(5) system命令
NAME system - execute a shell command // system 是用来执行command指定的命令或者程序的,不会覆盖当前进程的数据和指令 SYNOPSIS #include <stdlib.h> int system(const char *command); 如: system("ls -l"); system("/home/gec/sum_test 123 456"); system是调用/bin/bash来执行参数指定的命令 带有阻塞功能,command指定的命令或者程序没有执行完,就不会执行system函数下面的内容 阻塞的原因:system函数会创建进程去执行指定的程序,必须给创建的进程收尸 不收尸:内存泄漏
八. 练习
1. 假设下面的程序编译后的名字是main,请问这个程序执行后,系统总共会出现多少个main进程? 20个
int main() { fork(); fork() && fork() || fork(); fork(); }
2. 请问下面的程序执行结果为?
int main() { int i; for (i = 0; i < 2; i++) { fork(); printf("_\n"); } return 0; } _ _ _ _ _ _
3. 请问下面的程序执行结果为?
int main() { int i; for (i = 0; i < 2; i++) { fork(); printf("_ "); // 输出到缓冲区 } return 0; } _ _ _ _ _ _ _ _
4. 请问如下的程序中输出内容是什么?
int main() { fork() || fork(); printf("+"); return 0; } // +++ int main() { fork() && fork(); printf("+"); return 0; } // +++ int main() { fork() || fork() || fork(); printf("+"); return 0; } // ++++
5. 请问如下程序会生成多少个进程?
int main() { for (int i = 0; i < 5; i++) { int pid = fork(); } return 0; } // 32 个进程