目录
案列1
地址空间:包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。
案例15:验证execl返回值 (ps:注意路径的问题,只有在当前的路径才行)
好处:相比于system的好处就是,可以获取程序运行时的输出结果
1.进程的概念
1.1 进程的定义
程序:程序是存放在存储介质上的一个可执行文件。
进程:进程是程序的执行实例,包括程序计数器、寄存器和变量的当前值。
程序是静态的,进程是动态的
在 linux 系统中进程号(进程标识符)由 0 开始。
进程号为 0 及 1 的进程由内核创建。
进程号为 0 的进程通常是调度进程,常被称为交换进程(swapper)。进程号为 1 的进程通常是 init 进程。
除调度进程外,在 linux 下面所有的进程都由进程 init 进程直接或者间接创建。
1.2 在Linux系统中如何查看进程
ps命令、ps -aux命令
-------------------------------------------------------------------------------------------------------------------------------
查看特定的字段进程-----管道命令(grep)
grep init
-------------------------------------------------------------------------------------------------------------------------------
查看进程的占用率的情况----top命令
-------------------------------------------------------------------------------------------------------------------------------
进程号
每个进程都由一个进程号来标识,其类型为pid_t,进程号的范围:0~32767
进程号是由操作系统随机给当前进程分配的,不能自己控制进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用了
进程号(PID)
标识一个进程的非负整型数
父进程号(PPID)
任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进
程号(PPID)。
进程组号(PGID)
进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进
程有一个进程组号(PGID) 。
Linux 操作系统提供了三个获得进程号的函数 getpid()、getppid()、getpgid()。
需要包含头文件:
1 #include <sys/types.h>
2 #include <unistd.h>
3 pid_t getpid(void);
4 功能:获取当前进程的进程号
5 pid_t getppid(void);
6 功能:获取当前进程的父进程的进程号
7 pid_t getpgid(pid_t pid);
8 功能:获取当前进程所在进程组的
案列1
#include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main(int argc, char const *argv[])
6 {
7 //获取当前进程的进程号
8 printf("pid = %d\n", getpid());
9
10 //获取当前进程的父进程的id
11 printf("ppid = %d\n", getppid());
12
13 //获取当前进程所在组的id
14 printf("pgid = %d\n", getpgid(getpid()));
15
16 while(1)
17 {
18
19 }
20
21 return 0;
22 }
运行结果:
案例2:查看进程中的PID号
#include<stdio.h>
#include<sys/tyoes.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = getpid();
prinf("my pid is = %d\n",pid);
while(2); //防止进程终止
return 0;
}
~
~
~
~
~
运行结果:
使用top查看进程占用率
结果:可以看到当前进程由于while一直循环未退出,导致进程占用率百分之百
2进程的创建
2.1 进程的创建 fork 函数
在 linux 环境下,创建进程的主要方法是调用以下两个函数:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);pid_tvfork(void)
创建一个新进程
pid_t fork(void)
功能:
fork()函数用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。
失败:返回-1。
C程序的存储空间是如何分配
地址空间:
包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号
等。子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。
示意图(用fork函数的代价还是比较大的):
案例3:创建子进程(不区分父子进程 不推荐写)
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 int main(int argc, char *argv[])
5 {
6 //通过fork函数创建一个子进程
7
8 //注意:主要执行一次fork,就会在原有的进程基础上创建一个新的子进程
9 //而且如果fork之后不区分父子进程的代码区,则后面所有的代码都会执行
10 fork();
11 printf("hello world\n");
12
13 while(1)
14 ;
15 return 0;
16 }
hello打印两次,也就以为着fork函数创建了一个子进程
案例4:区分父子进程
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 int main(int argc, char *argv[])
5 {
6 //通过fork函数创建一个子进程7
#if 0
9 //注意:主要执行一次fork,就会在原有的进程基础上创建一个新的子进程
10 //而且如果fork之后不区分父子进程的代码区,则后面所有的代码都会执行
11 fork();
12 printf("hello world\n");
13
14 while(1)
15 ;
16 #endif
17
18 //通过fork函数的返回值来区分父子进程的独立的代码区
19 //父子进程是来回交替执行的,谁先运行,谁后运行是不确定的,不要认为父进程执
行完之后才会执行子进程
20 pid_t pid;
21
22 pid = fork();
23 if(pid < 0)
24 {
25 perror("fail to fork");
26 return ‐1;
27 }
28 else if(pid > 0) //父进程的代码区
29 {
30 while(1)
31 {
32 printf("parent: pid = %d, ppid = %d\n", getpid(), getppid());
33 printf("pid = %d\n", pid);
34 printf("this is a parent process\n");
35 sleep(1);
36 printf("****************\n");
37 }
38 }
39 else //子进程的代码区
40 {
41 while(1)
42 {
43 printf("son: pid = %d, ppid = %d\n", getpid(), getppid());
44 printf("this is a son process\n");
45 sleep(1);
46 printf("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐\n");
}
48 }
49
50 return 0;
51 }
说明两个情况:1.上面的等号关系
2.父子进程是交替执行
2.2 子进程继承父进程的空间
案例5:父子进程空间问题
结果:子进程是重新开辟的空间,与父进程无关
//子进程会复制父进程fork之前的所有内容
//但是fork之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区等),都不会收对方影响
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int a = 666;
6
7 int main(int argc, char *argv[])
8 {
9 pid_t pid;
10 static int b = 777;
11 int c = 888;
12
13 //子进程会复制父进程fork之前的所有内容
14 //但是fork之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区
等),都不会收对方影响
15
16 pid = fork();
17 if(pid < 0)
{
19 perror("fail to fork");
20 return ‐1;
21 }
22 if(pid > 0) //父进程的代码区
23 {
24 printf("This is a parent process\n");
25 a++;
26 b++;
27 c++;
28 printf("a = %d, b = %d, c = %d\n", a, b, c);
29 }
30 else //子进程的代码区
31 {
32 sleep(1);
33 printf("This is a son process\n");
34 printf("a = %d, b = %d, c = %d\n", a, b, c);
35 }
36
37 while(1)
38 {
39
40 }
41
42 return 0;
43 }
案例6: 子进程继承父进程的空间
结果:
子进程会继承父进程的一些公有的区域,不如磁盘空间,内核空间
文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的偏移量是改变之后的
#include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
int main(int argc, char *argv[])
9 {
10 int fd;
11 if((fd = open("file.txt", O_RDONLY)) == ‐1)
12 {
13 perror("fail to open");
14 return ‐1;
15 }
16
17 //子进程会继承父进程的一些公有的区域,不如磁盘空间,内核空间
18 //文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的
偏移量是改变之后的
19 pid_t pid;
20 pid = fork();
21 if(pid < 0)
22 {
23 perror("fail to fork");
24 return ‐1;
25 }
26 if(pid > 0)
27 {
28 printf("This is a parent process\n");
29
30 char buf[32] = "";
31 if(read(fd, buf, 30) == ‐1)
32 {
33 perror("fail to read");
34 return ‐1;
35 }
36
37 printf("buf = [%s]\n", buf);
38
39 }
40 else
41 {
42 sleep(1);
43 printf("This is a son process\n");
44
45 char buf[32] = "";
46 if(read(fd, buf, 30) == ‐1
{
48 perror("fail to read");
49 return ‐1;
50 }
51
52 printf("buf = [%s]\n", buf);
53 }
54
55 while(1)
56 {
57
58 }
59
60 return 0;
61 }
执行结果
案例7: 父子进程的pid返回值(补充)
结果:fork函数产生两个进程的原因是拷贝父进程直接给子进程
也就是说子进程是通过父进程直接拷贝而来的
运行结果
2.3 创建新进程的目的
目的:
①一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的一一父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
②一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec.
案例8:创建多个进程的作用
结果:输入对应的数字即可创建出新进程,并且创建后还能输入其他数字
#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;
}
运行结果:
2.4进程的创建 vfork 函数
案例9:使用vfork创建函数
结果:使用fork创建函数,父子进程交替运行
使用vfork创建函数,保证子进程先运行 , 当子进程调用 exit 退出后,父进
程才执行。
补充:vfork 直接使用父进程存储空间,不拷贝。
#include <stdio.h>
#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("cnt=%d",cnt);
printf("this is father print, pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(0);
cnt++;
if(cnt == 3){
exit(0);
// break; bunengshiyongbreak
}
}
}
return 0;
}
运行结果:
3.1 进程的退出
3.1正常退出和异常退出
正常退出
1 . Main 函数调用 return
2. 进程调用 exit(), 标准 c 库
3. 进程调用 _exit() 或者 _Exit() ,属于系统调用补充:
4. 进程最后一个线程返回
5. 最后一个线程调用 pthread_exit
exit(0);//也就是里面的数字作为一种退出时的状态,返回给调用函数
函数原型 :3种都可以使用,首要推荐exit
异常退出 :
1. 调用 abort
2. 当进程收到某些信号时,如 ctrl+C
3. 最后一个线程对取消( cancellation )请求做出响应
3.2 等待子进程退出
问题:为啥要等待子进程退出
答:收集子进程的状态
案例10:僵尸进程,子进程退出时,没有被收集
结果:子进程变成“Z+“
#include <stdio.h>
#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("cnt=%d\n",cnt);
printf("this is father print, pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(0);
cnt++;
if(cnt == 3){
//exit(0);
break;// bunengshiyongbreak
}
}
}
return 0;
}
运行结果:
防止出现僵尸进程
防止出现僵尸进程,可用wait函数来进行操作
也就是说exit()里面的返回值就是给wait进行调用的
案例11:添加wait后的效果
结果:如红字,并且还是使用的fork创建的更能体现先后顺序
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid > 0)
{
wait(NULL);
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print, pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5){
exit(0);
}
}
}
return 0;
}
案例12:检测子进程退出的状态
使用WEXITSTATU()函数收集退出的状态
运行结果
waitpid()函数
回顾一下案例12中的 wait(),使用wait时,必须要等子进程退出之后,父进程才能够运行,此时的父进程处于阻塞的状态,所以waitpid()就是为了解决这个阻塞
案例13:使用waitpid() (用的不多)
结果:父子进程可以交替运行,父进程不用阻塞
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = fork();
if(pid > 0)
{
// wait(&status);
waitpid(pid,&status,WNOHANG);
printf("child quit, child status = %d\n",WEXITSTATUS(status));
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print, pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is chilid print, pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
运行结果:
孤儿进程
①父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
②Linux 避免系统存在过多孤儿进程,init 进程收留孤儿进程,变成孤儿进程的父进程
案例14 验证②
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.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 chilid print, pid = %d,my father pid=%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 5){
exit(3);
}
}
}
return 0;
}
运行结果:
4.exec族函数
https://blog.csdn.net/u014530704/article/details/73848573
案例15:验证execl返回值 (ps:注意路径的问题,只有在当前的路径才行)
结果:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。并且把语句全部打印了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("./echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:说明excel没有调用成功
案例16:用excel函数代替ls显示文件列表
结果:显示成功,前提要知道ls在哪个位置,可以用whereis命令
实现ls -l则继续修改参数即可
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("before execl\n");
if(execl("/bin/ls","ls",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果
案例17:execlp() 不用加绝对路径实现ps命令
原理就是通过修改环境变量来实现
修改环境变量本质上就是不在当前目录下的文件也可以运行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
printf("this pro get system date:\n");
if(execlp("ps","ps",NULL,NULL) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:execlp()会在系统里面自己寻找环境变量
案例18:使用execvp() 函数
更加要强化argv数组指针的作用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//函数原型:int execvp(const char *file, char *const argv[]);
int main(void)
{
printf("this pro get system date:\n");
char *argv[] = {"ps",NULL,NULL};
if(execvp("ps",argv) == -1)
{
printf("execl failed!\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:完全一样
案例19:exec和fork配合使用
直接用execl()函数使得代码更加简洁
原先的代码1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
int fdSrc;
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
char *readBuf=NULL;
fdSrc = open("config.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET);
readBuf=(char *)malloc(sizeof(char)*size + 8);
int n_read = read(fdSrc, readBuf, size);
char *p = strstr(readBuf,"LENG=");
if(p==NULL){
printf("not found\n");
exit(-1);
}
p = p+strlen("LENG=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
exit(0);
}
}
else{
printf("wait ,do nothing\n");
}
}
return 0;
}
修改后的代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
int fdSrc;
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
execl("./changData","changData","config.txt",NULL);
}
}
else{
printf("wait ,do nothing\n");
}
}
return 0;
}
运行结果
使用execl后
5.system函数
说白了就是封装好的excel()
https://www.cnblogs.com/leijiangtao/p/4051387.html
system源码
案例20:用system函数代替execl函数
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
int fdSrc;
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
// execl("./changData","changData","config.txt",NULL);
system("./changData config.txt");
}
}
else{
printf("wait ,do nothing\n");
}
}
return 0;
}
运行结果:一样
案例21:用system函数代替ps命令,查看进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
int main(void)
{
//char ret[1024] = {0};
if(system("ps")==-1){
printf("execl failed!\n");
perror("why:");
}
// system("ps");
printf("after execl\n");
return 0;
}
运行结果:相较于execl不同的是,system最后还会返回到源程序中执行后面的代码3
6.popen函数
好处:相比于system的好处就是,可以获取程序运行时的输出结果
参考文章:linux下popen的使用心得_linux c++ popen 命令执行结束-CSDN博客
首先要搞清楚的就是popen的函数原型
#include “stdio.h” FILE popen( const char command, const char* mode )
可以看到该函数的返回值是一个 “文件流”
案例22:获取指令的结果,并且打印出来
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
int main(void)
{
char ret[1024] = {0};
FILE *fp;
fp = popen("ps","r");
int nread = fread(ret,1,1024,fp); //将fp里面的东西读到ret里面
printf("read ret %d byte, ret=%s\n",nread,ret);
return 0;
}
运行结果: