*1、Linux系统编程–进程相关概念
文章目录
1.1、什么是程序,什么是进程,有什么区别?
1.1.1 程序
- 程序是静态的概念,
gcc xxx.c -o pro; 磁盘中生成pro文件,叫做程序;
1.1.2 进程
- 进程是程序的一次运动活动,通俗点意思是程序跑起来了,系统中就多了一个进程;
1.1.3 区别
- 程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
- 程序是静态的观念,进程是动态的观念;
- 进程具有并发性,而程序没有;
- 进程是竞争计算机资源的基本单位 ,程序不是。
1.2、如何查看系统中有哪些进程?
- 命令
ps -aux列出所有进程; - 命令
ps -aux|grep init把含**init**的进程筛选出来 - 命令 **
top**查看进程(类似于window任务管理器)
1.3、什么是进程标识符?
1.3.1 进程标识符
- 每个进程都有一个非负整数表示的唯一
ID,叫做pid,类似身份证;Pid = 0:称为交换进程(swapper)- 作用:进程调度 //由它来决定,当前某一时刻由谁来跑;
Pid = 1:init进程- 作用:系统初始化 //刚开始就应该执行的程序
1.3.2 getpid /getppid 函数
pid_t getpid(void); //获取自身的进程标识符;
pid_t getppid(void); //获取父进程的标识符;
- 添加头文件
#include <sys/types.h>
#include <unistd.h>
-
getpid获取自身的进程标识符; -
getpid获取父进程的标识符;
- 程序演示
//demo.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
printf("my pid is %d\n",pid);
while(1); //定住
return 0;
}
// 编译 然后将输出输入到hello文件中
gcc -o hello hello_world.c
// 执行
./hello
// 显示内容:Hello World
- 再打开一个终端,输入命令
top,可见该进程正在执行;
1.4、什么是父进程,什么叫子进程?
- 进程A创造了进程B (A --> B)
- 进程A叫做父进程,B叫做子进程;
- 父子进程是相对的概念没理解为人类中的父子关系;
1.5、C程序的存储空间如何分配?

- 代码段: if else 等逻辑语句;
- 数据段: 初始化的数据 int a = 0;
bss段:未初始化的变量;- 栈:
calloc申请内存地址; - 堆: 函数地址,以及函数中所产生的局部变量;

*2、Linux系统编程–创建进程
2.1 fork()
pid_t fork(void);
- 创造一个子进程;
- fork 调用成功(返回2下)
- 给父进程返回 非负数 且 正好为子进程的 ID 号;
- 给子进程返回 0;
调用失败:返回 -1;
- 添加头文件
#include <sys/tupes.h>
#include <unistd.h>
- 程序演示
//demo4.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("father:id=%d\n",getpid());
pid = fork(); //创造一个子进程
if(pid > 0)
{
printf("this is father print,pid = %d\n\n",getpid());
}
else if(pid == 0)
{
printf("this is child print pid =%d\n",getpid());
}
return 0;
}
2.2 进程创建发生了什么事
- 子进程不改变 变量a,共享;
- 子进程改变 变量a,从父进程里面拷贝一份变量a的地址给子进程;
2.3 fork创建一个子进程的一般目的
- 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这是请求到达时,父进程调用**
fork**,使子进程处理此请求。父进程则继续等待下一个服务请求到达。 - //一个进程要执行一个不同的程序。这对 shell 是很常见的情况。在这种情况下,子进程从fork返回后立即调用exec;
//demo5.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int data = 10;
while(1)
{
printf("please input a data\n");
scanf("%d",&data);
if(data == 1)
{
pid = fork();
if(pid > 0)
{
}
else if(pid ==0)
{
while(1)
{
printf("do net request,pid = %d\n",getpid());
sleep(3);
}
}
}
else
{
printf("wait, do nothing\n");
}
}
return 0;
}
- 父进程一直检测客服端用户输入,每当用户输入”1“,创建一个子进程,每个子进程都不断输出自己的 ID 号;
- 父进程和子进程互不影响;
2.4 小总结

2.5 vfork()
vfork( )与fork( )的区别vfork直接使用父进程存储空间,不拷贝。vfork保证子进程先运行,当子进程调用 exit 推出后,父进程才执行。
//demo6.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1)
{
printf("this is father print,pid = %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(0); //退出 0 代表这个子进程退出的状态
//_exit(0);
//_Exit(0);
//不可以用 break 推出,会使cnt混乱
}
}
}
return 0;
}
3、Linux系统编程–进程退出
3.1 正常退出
Main函数调用return;- 进程调用**
exit()**,标准c库; - 进程调用**
_exit()**或者_Exit(), 属于系统调用;
- 补充
- 进程最后一个线程返回了;
- 最后一个线程调用
pthread_exit;
3.2 异常退出
- 调用
abort; - 当进程收到某些信号时,如
ctrl + C; - 最后一个线程对取消**
(cancellation)**请求做出响应;
#include <stdio.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Eixt(int status);
3.3 小总结

4、Linux系统编程–父进程等待子进程退出
- 为什么等待,要干活

4.1 wait() /waitpid()
**僵尸进程:**子进程退出状态不被收集,变成僵死进程
孤儿进程 :父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程;
-
Linux避免系统存在过多的孤儿进程,
init进程收留孤儿进程,变成孤儿进程的父进程; -
进程状态:
- 命令:
ps -aux|grep a.out
S+ 正在运行;
Z+ 僵尸进程 (zombie)
- 命令:
4.1.1 检查wait 和waitpid 所返回的终止状态的宏-解析退出码

来说明是什么原因退出的;
- 解析status 退出码,也就是exit(?) 里的参数;
int status;
//子进程
exit(3);//退出码 ? = 3
//父进程
wait(&status); //里面的退出码给 *status;
WEXITSTATUS(status) //== 3 //返回"exit status"//解析退出码
- 父进程等待子进程退出,并收集子进程的退出状态;
4.1.2 wait 函数和 waitpid 函数介绍

- 将exit( ? ),里面的退出码给 * status;
- 如果其所有子进程都还在运行,则阻塞;
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回;
- 如果没有任何子进程,则立即出错返回;
-
status参数:(是一个整型数指针)
exit(3)status = 3;- 非空:子进程退出状态放在它所指向的地址中;
- 空: 不关心退出状态;
-
wait和waitpid的区别wait使调用者阻塞;waitpid有一个选项,可以使调用者不阻塞;waitpid等待一个指定的子进程,而wait等待所有的子进程,返回任一终止子进程的状态;
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork(); //创建子进程
if(pid > 0)
{
wait(&status);//等待子进程结束,防止子进程变成僵尸进程;
printf("child quit,child status = %d\n",WEXITSTATUS(status));//返回的终止状态的宏WEXITSTATUS(*status)
while(1)
{
printf("this is father print,pid = %d\n",getpid());
sleep(1);
printf("cnt = %d\n",cnt);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child print,pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(3); //退出
//_exit(3);
//_Exit(3);
}
}
}
return 0;
}
4.1.3 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绝对值的任一子进程;
-
options

4.2 孤儿进程
-
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程;
-
- Linux避免系统存在过多的孤儿进程,
init进程收留孤儿进程,变成孤儿进程的父进程
- Linux避免系统存在过多的孤儿进程,
//demo8.c 演示出孤儿进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();//创建子进程;
if(pid > 0)//父进程执行
{
printf("this is father print,pid = %d\n",getpid()); //打印一次,父进程就死了,死的比子进程早
}
else if(pid == 0)//子进程执行
{
while(1)
{
printf("this is child print,pid = %d, my father pid = %d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt ==3)
{
exit(3); //退出
//
}
}
}
return 0;
}

- 结果
- 父进程打印一次
- 子进程打印一次
- 父进程死了,子进程被
init进程(896) 收留,成为了init进程(896) 的子进程
5、Linux系统编程–exec族函数
(execl, execlp, execle, execv, execvp, execvpe)
推荐 blog:https://blog.youkuaiyun.com/u014530704/article/details/73848573
-
exec族函数函数的作用:
- 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
-
功能:
在调用进程内部执行一个可执行文件。既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 -
函数族:
exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe -
函数原型
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
//int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
//int execvpe(const char *file, char *const argv[],char *const envp[]);
//结尾带e的不常用
- 返回值:
* exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。 - 参数说明:
path:可执行文件的路径名字arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
// e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
建议看推荐 blog
5.1 常用命令和函数
- 命令
whereis ls: 去找 ls 的绝对路径 - 打印输出函数 :
perror(" ? why ?"): 类似于printf();perror(s)用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
5.2 小功能–获取系统时间
- 命令:
date;

- 命令:
whereis date//获取date的绝对路径

//demo10.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("this pro get system date:\n");
if(execl("/bin/date","date",NULL,NULL)== -1) //不加参数,所有第三个形参为NULL;
{
printf("execlp failed!\n");
perror("why"); //输出上一个函数发生错误的原因
}
printf("after execlp***\n");
return 0;
}
5.3 更改环境变量PATH
-
命令
pwd//获取当前路径

-
命令
echo $PATH//显示环境变量

-
命令
export PATH =$PATH:/...

*6、Linux系统编程–system / popen
6.1 常用命令和函数
- cat 查看体积较大的文件
6.2 system()
blog: <linux system函数详解 - 南哥的天下 - 博客园 (cnblogs.com)>
在system源码中,可以看出,system 是
execl函数的封装,程序员也更愿意去用。
#include <stdio.h>
int system(const char *command)
- 作用:执行一个
shell指令 - 返回值:
- 成功:返回进程的状态值;
- 不能执行:返回 127 ;
- 失败:返回 -1 ;
system()与execl()的区别execl这个函数系列 他会代替调用它的程序 执行完成后 不会回到主调程序中 直接用新的execl create的shell替代了 原来的程序system这个函数不同 他会fork一个子程序中 但他会在主调程序中等待 system的返回。 相当于for+execl+waitpid三个函数的合体
//demo11.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("this pro get system date:\n");
//if(execl("/bin/ps","ps",NULL,NULL)== -1) //不加参数,所有第三个形参为NULL;
//这就是execl 与system 的区别;
if(system("ps")== -1)
{
printf("execlp failed!\n");
perror("why");
}
printf("after execlp***\n");
return 0;
}
6.3 popen()
6.3.1 一些小常识
- 涉及到
FILE,文件,我们就说 “流”;fopen、 fread...
6.3.2 popen() 用法介绍
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream); //关闭“流”;
- 返回一个文件流,
FILE *fp;
fp = popen("ps","r");
command:是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到bin/sh并使用 -c 标志,shell 将执行这个命令。mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。- 如果 type 是 “r” 则文件指针连接到 command 的标准输出;
- 如果 type 是 “w” 则文件指针连接到 command 的标准输入;
6.3.3 popen() 的优势
- 相较
system来说,popen可以获取运行的输出结果;
//demo12.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024] = {0};
FILE *fp;
//system("ps");
//如何保存输出的数据到file文件呢?
//不用system利用popen;
fp = popen("ps","r");
// size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
int nread = fread(ret, 1,1024,fp);
//nread: fread返回读了几个数;
printf("read ret %d byte,ret = %s\n",nread,ret);
return 0;
}
- 实验结果

欢迎大家一起交流讨论
本文围绕Linux系统编程中进程相关内容展开,介绍了进程与程序的区别、进程标识符、父进程与子进程等概念,还阐述了创建进程、进程退出、父进程等待子进程退出等操作,以及exec族函数、system函数等的使用和特点。

被折叠的 条评论
为什么被折叠?



