*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.csdn.net/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;
}
- 实验结果
欢迎大家一起交流讨论