linux进程

一.进程关键概念

我们首先需要了解下面5个问题:

问题一:什么是程序?什么是进程?有什么区别?

问题二:如何查看系统中有哪些进程?

问题三:什么是进程标识符?

问题四:什么叫父进程,什么叫子进程?

问题五:c程序的存储空间是如何分配的?

1.什么是程序?什么是进程?有什么区别?
(1)程序就是静态的概念,也就是在磁盘中生成的文件。
(2)进程是程序的一次运行活动,通俗点来说就是程序跑起来,系统中就多了一个进程。 (动态)

2.如何查看系统中有哪些进程?
(1)ps指令查看 (配合grep)

例:ps -aux 查看全部进程
如果想要查看指定进程,我们就需要把其他进程过滤掉。 (配合grep)
ps -aux|grep init 查看init进程,过滤掉其他进程
(2)top指令查看,(类似windows的任务管理器)

例:在命令行输入 top 即可查看全部进程。

3.什么是进程标识符?
(1)每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
在操作系统中,默认
Pid=0 : 称为交换进程(swapper)
作用: 进程调度

Pid=1 : init进程
作用 : 系统初始化

编程调用getpid函数获取自身的进程标识符
getppid获取父进程的进程标识符


demo1.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); //打印该进程的id

	while(1);            
	return 0;
}

运行结果:
在这里插入图片描述


4.什么叫父进程,什么叫子进程?
进程A创建了进程B
则进程A叫做父进程,B叫做子进程。

5.c程序的存储空间是如何分配的?
在这里插入图片描述
1.正文(代码段):程序流程控制,算法之类的代码。
2.数据段:初始化过的变量。
函数外未被初始化的数据,叫做bss段
3.:通常在堆中进行动态存储分配。
调用malloc相关的函数去申请空间,返回的内存地址都在堆空间里。
4.:函数调用以及函数的局部变量和产生的信息放到栈里。
5.命令行参数和环境变量:如 argc argv等




二.创建进程函数fork的使用

使用fork函数创建一个进程
pid_t fork(void);
fork函数调用成功,返回两次;
返回值为0, 代表当前进程是子进程
返回值为1, 代表当前进程是父进程。
若调用失败,返回-1.

demo_2_1.c
理解getpid函数和fork函数

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
	pid_t pid;
	pid = getpid(); //获取自身(当前)的进程
	fork(); //创建一个新的进程,fork()以后就有2个进程在跑.
	printf("my pid is %d,current pro id:%d\n",pid,getpid());
	//不同的两个进程输出两个不同的id; 
	//pid一样,但两个getpid()的结果不同
	return 0;
}

输出结果:(仔细观察两次进程的不同)

在这里插入图片描述


demo_2_2.c
fork函数调用成功,返回两次;
返回值为0, 代表当前进程是子进程
返回值为非负数, 代表当前进程是父进程。

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
	pid_t pid;
	printf("father id:%d\n",getpid());
     //创建了一个新的进程,但原来的进程还会继续执行下去
     //新进程的id赋值给了pid,所以此时子进程的 pid==0,所以两个都会打印。
	pid = fork();
	if(pid > 0)
	{
		printf("father progess id:%d\n",getpid());//father
	}
	else if(pid == 0)
	{
		printf("child progess id:%d\n",getpid());//child
	}
	//返回2个,所以if和else if 的语句都会被执行
	return 0;
}

输出结果:
在这里插入图片描述


demo_2_3.c
查看不同进程的id,
当父进程创建了子进程之后 ,几乎是两个进程同时在跑
fork()
父进程的返回值是子进程的id;
子进程的返回值是0;

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
	pid_t pid1;
	pid_t pid2;
	pid_t newpid;   //定义一个新的id
	pid1 = getpid();
	printf("father id:%d\n",pid1);

	newpid = fork();//开辟了子进程,相当于两个通道,父子进程各自在各自的进程跑,也就是各自在各自的通道执行下面的程序。
	pid2 = getpid();
	printf("after id:%d\n",pid2);
	if(pid1 == pid2)
	{
		printf("father progess id:%d\n",getpid());//father
		printf("newpid:%d\n",newpid);// newpid>0,  返回的是子进程的 id
	}
	else
	{
		printf("child progess id:%d\n newepid:%d\n",getpid(),newpid);//child
		//
	}
	return 0;
}

输出结果:
在这里插入图片描述



三.fork编程实战

fork一般的应用场景: 服务器和客户端的通信
demo_3.c
简单模拟一下,了解fork的使用

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
	pid_t pid;
	int data;
	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;
}


四.fork和vfork创建进程+进程退出

1.fork创建子进程的一般目的
(1)一个父进程希望复制自己,使父子进程同时执行不同的代码段。(在网络服务进程中常见——— 父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求到达。)
(2)一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec.

2.由fork创建的新进程被称为子进程,fork被调用一次,但返回两次;
两次返回的唯一区别是子进程的返回值是0 (一个进程只会有一个父进程,所以子总是可以调用getppid以获得其父的进程ID)(进程ID: 0 总是由内核交换进程使用,所以一个子进程的进程ID不可能为0). 而父进程的返回值是新进程的ID,(一对多,但是没有一个函数使一个进程获得所有子进程的进程ID);

3. 子进程是父进程的副本.
例:子进程获得父进程数据空间,堆,栈的副本。(注意:这是子进程所拥有的副本,父子进程并不共享这些存储空间部分,父子进程共享正文段。)

4.vfork函数 也可以创建进程,与fork有什么区别?
关键区别一:

vfork直接使用父进程存储空间,不拷贝。

关键区别二:

vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

观察下面demo,了解vfork的用法;
demo_4.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("father pid:%d\n",getpid());
			sleep(1);
			printf("cnt = %d\n",cnt);
		}
	}	
	else if(pid == 0)
	{
		while(1)
		{
			printf("child pid:%d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 3)
			{
				exit(0); // 子进程退出的时候一定要保证是用较好的方式退出,要不然容易被破坏掉。
				//子进程先运行,当子进程调用exit退出后,父进程才执行
			}
		}
	}
	return 0;
}

运行结果:
在这里插入图片描述
5.进程退出
(1)正常退出:

_1. main函数调用return退出
_2.进程调用exit(),标准c库退出
_3.进程调用_exit()或_Exit(), 属于系统调用退出
_4.进程最后一个线程返回
_5.最后一个线程调用pthread_exit 退出

(2)异常退出

_1.调用abort
_2.当进程收到某些信号时,如Ctrl+C
_3.最后一个线程对取消(cancellation),请求做出相应




五.父进程等待子进程退出

1.父进程等待子进程退出,并收集子进程的退出状态。

等待: wait函数

wait(状态码) :
子进程调用 exit()把状态码返回来,wait解析状态码(需要用到下面的宏)。

在这里插入图片描述
demo_5.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t pid;
	int cnt = 0;
	int status=6;
	pid = fork();
	if(pid > 0)
	{
		wait(&status);// 等待子进程退出,接收返回的状态码
		printf("Child quit,child stauts = %d\n",WEXITSTATUS(status));//打印 3   解析状态码
		while(1)
		{
			
			printf("father pid:%d\n",getpid());
			sleep(1);
			printf("cnt = %d\n",cnt);
		}
	}	
	else if(pid == 0)
	{
		while(1)
		{
			printf("child pid:%d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 3)
			{
				exit(3); //退出,返回3
			}
		}
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

2.子进程退出状态不被收集,变成僵死进程。
**僵死进程 **

参考上面demo_4.c, (僵死进程)(zombie)

————————————————————————————————
wait函数
在这里插入图片描述
3.wait和waitpid的区别

wait使调用者阻塞 ,waitpid有一个选型,可以使调用者不阻塞。
此处可以去百度查询waitpid的用法

4.孤儿进程
------父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
——Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
demo_6.c

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
	pid_t pid;
	int status=10;
	int cnt=0;

	pid = fork();

	if(pid > 0)
	{
		printf("this is father print, pid=%d\n",getpid());

	}
	else if(pid == 0)
	{
		while(1)
		{
			printf("this is child,pid=%d, my father pid=%d\n",getpid(),getppid());  //getppid() 获取父进程的pid
			sleep(1);
			cnt++;
			if(cnt == 5)
			{
				exit(3);
			}
		}
	}

	return 0;
}

运行结果:
init进程收留孤儿进程,变成孤儿进程的父进程。
getppid() 获得的pid 为init进程的pid。




六.exec族函数

参考(精彩)博文:exec组函数
https://blog.csdn.net/u014530704/article/details/73848573

1.exec族函数的作用,定义以及函数原型
exec族函数的作用:
——我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
在这里插入图片描述
在这里插入图片描述
demo_6_1.c
先写个这样的程序:

#include <stdio.h>

int main(int argc,char *argv[])
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]); 
    }
    return 0;
}

再写另外一个程序:
注意perror函数的用法:查看程序出错的原因

execl

//文件pro_6_2.c
#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("./pro_6","pro_6","abc",NULL) == -1)
    {
        printf("execl failed!\n");   
        perror("why");//查看失败的原因,perror()	
    }
    printf("after execl\n");
    return 0;
}

结果:
在这里插入图片描述

2.whereis + 指令 :查询指令的目录
例:
—— whereis ls 查找ls的根目录


先了解上面参考博文的一些用法,之后再举例子简单了解下:
3.通过execl函数查询系统时间
首先在linux查询时间非常简单, 直接在终端输入:date
c语言实现:
(1)现在终端输入:whereis date
——————查看date的根目录
(2)通过c语言实现终端输入date指令
demo_7.c

//文件pro_6_3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("this is system date:\n");
    if(execl("/bin/date","date",NULL,NULL) == -1)
    {
        printf("execl failed!\n");   
        perror("why");//查看失败的原因,perror()	
    }
    printf("after execl\n");
    return 0;
}

在这里插入图片描述

      • exec函数族:execl, execlp, execle, execv, execvp, execvpe
        可通过查看参考博文了解。

4.exec族函数配合fork使用

为什么要用exec族函数,有么用?

  • 一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec.

demo.c
实现功能:- 当父进程检测输入为1 时,创建子进程把配置文件的字段值修改掉。

//pro_7.c
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>



int main()
{
	pid_t pid;
	int data;
	while(1)
	{
		printf("please input a data:\n");
		scanf("%d",&data);
		if(data == 1)
		{
			pid = fork();//创建一个子进程
			if(pid > 0)
			{
				wait(NULL);
			}
			else if(pid == 0)
			{
				execlp("./changedata","changedata","config.txt",NULL);
				// 在子进程中执行changedata 
				// 这块的changedata是通过 gcc changedata.c -o changedata	之后的可执行程序。
			}
		}
		else
		{
			printf("wait,do nothing!\n");
		}
	}
	return 0;
}

修改程序的配置文件 changedata:

//changedata.c
#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(int argc,char **argv)
{	
    int fdSrc;	
    char *readbuf = NULL;	
    if(argc != 2)	
    {		    
         printf("params error\n");//params 参数		
         exit(-1);
   	}	
     fdSrc = open(argv[1],O_RDWR);    	//打开src文件	
     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);   //读取src文件内容,存储到readbuf中	//char *strstr(const char *haystack, const char *needle);
      char *p = strstr(readbuf,"LENG=");	
      if(p == NULL)	
      {		
         	printf("No found\n");		 
            exit(-1);	
       }	
       p = p + strlen("LENG=");	
       *p = '5';	
       lseek(fdSrc,0,SEEK_SET);	
      int n_write =write(fdSrc,readbuf,strlen(readbuf));//将readbuf里的内容写入到Des文件中	
       close(fdSrc);
       return 0;
}
  • 修改config.txt的内容
  • 未执行程序前的config.txt
    在这里插入图片描述
  • 程序执行后
  • 在这里插入图片描述



七.system函数

1.system原型
参考经典博文:system用法
在这里插入图片描述
在这里插入图片描述

demo_7.c
对exec组函数的demo进行修改, 修改配置 文件的内容

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdlib.h>

int main()
{
	pid_t pid;
	int data;
	while(1)
	{
		printf("please input a data:\n");
		scanf("%d",&data);
		if(data == 1)
		{
			pid = fork();//创建一个子进程
			if(pid > 0)
			{
				wait(NULL);
			}
			else if(pid == 0)
			{
//				execlp("./changedata","changedata","config.txt",NULL);
				system("./changedata config.txt");
				//相当于在命令行直接输入 ./changedata config.txt
			}
		}
		else
		{
			printf("wait,do nothing!\n");
		}
	}
	return 0;
}

demo_7_2.c

  • 查看系统时间, 类似于上面的execl对date的操作
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("this is system date:\n");
    //if(execl("/bin/date","date",NULL,NULL) == -1)
    if(system("date") == -1)
    {
        printf("execl failed!\n");   
        perror("why");//查看失败的原因,perror()	
    }
    printf("after execl\n");
    return 0;
}

注意: 还是要注意system与exec族函数的区别,
exec组函数 执行成功后,不会打印后面的 printf(“after execl\n”);
system函数 执行成功后,还是会打印后面的 printf(“after execl\n”);




八.popen函数

popen比system在应用中好处是:popen可以获取输出结果

#include<stdio.h>
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

int main()
{
	FILE *fp;
	FILE *fp2;
	char ret[1024] = {0};

	fp = popen("ps","r");  // 执行ps指令,  并且可以获取输出结果
	int n_read = fread(ret,1,1024,fp);
	printf("read ret:%d byte\ncontext:%s\n",n_read,ret);
    
    //还可以把内容写入到ps.txt文件中
	fp2 = fopen("./ps.txt","w+");
	fwrite(ret,1,1024,fp2);
	fclose(fp2);
	return 0;
}

平时笔记,忘了就来这里翻- - - - - - - - - - -
笔记终于写完了,good!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值