一篇文章了解Linux下进程相关知识

        pid_t pid;//定义一个进程标识符
        pid = getpid();//获取当前进程的pid
        fork();//创建进程
        fork()是有返回值的,返回值为0代表当前进程是子进程,返回值为非负数代表当前进程是父进程,
        返回-1代表调用失败
        我们来了解一下vfork函数,那vfork和fork有什么区别呢?
		区别一:vfork直接使用父进程存储空间,不拷贝
		!!!区别二:vfork保证子进程先运行,当子进程调用exit()退出后,父进程再运行**
		break;//非正常退出
		exit(0);//正常退出
		wait(&status),//收集子进程退出状态,等待子进程退出再运行父进程
		WEXITSTATUS(status)//显示真正的退出状态
		异常退出会造成子进程变为僵尸进程,使用wait(NULL)。//等待子进程先运行,子进程运行完父进程运行,并且手机子进程退出状态。

1、什么是程序,什么是进程,有什么区别?
程序是静态的概念,进程是动态的概念。gcc a.c -o a,a就是一个程序,存在于硬盘中,当a跑起来之后,系统中就 多了一个进程,进程就是跑起来的程序。
2、什么是进程标识符?
每个进程都有唯一的非负整数表示唯一ID,叫做pid,类似进程的身份证
pid = 0,交换进程,作用是进程调度
pid = 1,init进程,作用是系统初始化
用top来查看进程的pid以及占用cup率,以评估程序好坏。
ps -aux|grep snake 筛选出进程为snake的相关进程

#include 
#include 
#include 
int main()
{
        pid_t pid;//定义一个进程标识符
        pid = getpid();//获取当前进程的pid
        printf("my pid is %d\n",pid);
        while(1);
        return 0;
}
~   

一篇文章了解Linux下进程相关知识_第1张图片

3、什么是父进程,什么是子进程
A进程创建了B进程,则称A进程是B进程的父进程,B进程是A进程的子进程,父子进程是相对的概念。
4、C程序的存储空间是如何分配的?
一篇文章了解Linux下进程相关知识_第2张图片
malloc申请的空间返回值就是在堆里
寄存器:最快的存储区
一、创建进程实战

pid_t fork(void);
fork函数调用成功返回两次,
fork = 0,表示当前进程是子进程
fork返回值是非负数,表示当前进程是父进程

调用失败返回﹣1

#include 
#include 
#include 
int main()
{
        pid_t pid;
        pid = getpid();
        printf("before fork pid is %d\n",getpid());
        fork();//在此处创建进程
		printf("after fork pid is %d\n",getpid());
		if(pid == getpid())
		{
			printf("this is father fork\n");
		}
		else
		{
			printf("this is child fork,child pid is %d\n",getpid());
		}
        return 0;
}

一篇文章了解Linux下进程相关知识_第3张图片
这里我们可以分析得出,创建进程就是**把fork后面的代码(包括fork行)**重新运行一次,不过得父进程运行完成之后再来运行子进程

我们通过fork返回的值来判断父子进程

#include 
#include 
#include 
int main()
{
        pid_t pid;
        pid = getpid();//先把父进程的pid打印出来,动态值赋值给静态
        printf("this is father fork,pid is %d\n",pid);
        pid = fork();//在此处创建进程
                if(pid>0)
                {
                        printf("this is father fork,pid is %d\n",getpid());
                }
                else if(pid == 0)
                {
                        printf("this is child fork,child pid is %d\n",getpid());
                }
        return 0;
}

一篇文章了解Linux下进程相关知识_第4张图片
那父子进程创建过程中发生了什么呢?

#include 
#include 
#include 
int main()
{
        pid_t pid;
        pid = getpid();
		int data = 10;
        printf("this is father fork,pid is %d\n",pid);
        pid = fork();//在此处创建进程
		if(pid>0)
		{
			printf("this is father fork,pid is %d\n",getpid());		
		}
		else if(pid == 0)
		{
			printf("this is child fork,child pid is %d\n",getpid());
			data = data + 100;
		}
		printf("data = %d\n",data);
        return 0;
}

一篇文章了解Linux下进程相关知识_第5张图片
从程序的运行结果可以看出,当子进程运行时,程序从父进程备份了一份数据,在对子进程操作时,改变的是子进程的数据,不会对父进程数据造成影响。

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

需求1应用场景:

#include 
#include 
#include 

int main()
{
        int mark;
        pid_t pid;
        while(1)
        {
                printf("please input mark:\n");
                scanf("%d",&mark);
                if(mark == 1)
                {
                        pid = fork();
                        if(pid > 0)
                        {
                                printf("this is father process\n");
                        }
                        else if(pid == 0)
                        {
                                while(1)
                                {
                                        printf("no request,pid is %d\n",getpid());
                                        sleep(7);
                                }
                        }
                }
                else
                {

                        printf("do nothing");
                }

        }


}
//fork()函数创建的子进程会和父进程争夺cpu,不一定一先一后。

#include 
#include 
#include 
#include 
int main()
{
	int mark;
	pid_t pid;
	int cnt = 0;
	while(1)
	{
		printf("please input mark:\n");
		scanf("%d",&mark);
		if(mark == 1)
		{
			pid = vfork();
			if(pid > 0)
			{
				printf("this is father process:%d\n",getpid());
				printf("cnt = %d\n",cnt);
				sleep(2);
			}
			else if(pid == 0)
			{
				while(1)
				{
					cnt++;
					printf("no request,pid is %d\n",getpid());
					if(cnt == 3)
					{
						cnt = 0;
						exit(0);
					}
					sleep(2);
				}
			}
		}
		else 
		{
		
			printf("do nothing\n");
			sleep(0.5);
		}
	
	}


}

在这里插入代码片

一篇文章了解Linux下进程相关知识_第6张图片
父子进程两者都是同时运行,父进程一直监测用户输入,当有客户端来时,创建子进程服务于客户端。父进程只检测什么也不干。这里我们可以看出当未创建服务端时,父进程一直在等待输入,当服务端来临时,立刻创建一个子进程,同时父进程也在运行等待新的输入,最后我们输入三个1意味着创建了三个子进程,这三个子进程同时运行。
!!!核心:创建子进程时,子进程copy了父进程的代码部分存到存储器,这时共有两份数据同时运行。

我们来验证一下父子进程同时在运行的结果:

#include 
#include 
#include 
int main()
{
	pid_t pid;
	pid = fork();
	if(pid>0)
	{
		while(1)
		{
			printf("this is father fork,pid is %d\n",getpid());
			sleep(1);
		}
	}
	else if(pid == 0)
	{
		while(1)
		{
			printf("this is fchild fork,pid is %d\n",getpid());
			sleep(1);
		}
	}
	return 0;
}

一篇文章了解Linux下进程相关知识_第7张图片
这里我们可以看到父子进程同时运行,在争夺CPU的资源

我们来了解一下vfork函数,那vfork和fork有什么区别呢?
区别一:vfork直接使用父进程存储空间,不拷贝
区别二:vfork保证子进程先运行,当子进程调用exit()退出后,父进程再运行

下面我们来验证一下:

#include 
#include 
#include 
int main()
{
	pid_t pid;
	pid = vfork();
	int cnt = 0;
	if(pid>0)
	{
		while(1)
		{
			printf("this is father fork,pid is %d\n",getpid());
			sleep(1);
			printf("cnt = %d\n",cnt);
		}
	}
	else if(pid == 0)
	{
		while(1)
		{
			printf("this is child fork,pid is %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 3)
			{
				break;//非正常退出
			}
		}
	}
	return 0;
}

一篇文章了解Linux下进程相关知识_第8张图片

#include 
#include 
#include 
#include 
int main()
{
        pid_t pid;
        int cnt = 0;
        pid = vfork();
        if(pid>0)
        {
                while(1)
                {
                        printf("this is father fork,pid is %d\n",getpid());
                        sleep(1);
                        printf("cnt = %d\n",cnt);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is child fork,pid is %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3)
                        {
                                exit(0);//正常退出
                        }
                }
        }
        return 0;
}

一篇文章了解Linux下进程相关知识_第9张图片

这里先让子进程运行三次,然后退出执行父进程。这里我们可以看出当程序异常退出时会改变cnt的值,我们必须使用exit使程序正常退出。
这个时候调用ps查看进程我们可以知道这个子进程为zombie进程即为僵尸进程,进程运行完后不收集会变成僵尸进程。
一篇文章了解Linux下进程相关知识_第10张图片
*为了避免僵尸进程,我们调用wait函数。
pid_t wait(int status);
它的参数是一个指针,指针为空时,不关心退出状态,非空时子进程退出状态放在它所指向的地址中;同样我们调用fork函数来验证一下wait函数的作用

一篇文章了解Linux下进程相关知识_第11张图片

一篇文章了解Linux下进程相关知识_第12张图片

这里我们可以看到程序的运行结果,父进程先等待子进程运行,子进程运行完成后,父进程运行,并释放子进程,此时系统中无僵尸进程。

#include 
#include 
#include 
#include 
int main()
{
        pid_t pid;
        pid = vfork();
        int status = 100;
        int cnt = 0;
        if(pid>0)
        {
                wait(&status);
                while(1)
                {
                        printf("the status is %d\n",WEXITSTATUS(status));
                        printf("this is father fork,pid is %d\n",getpid());
                        sleep(1);
                        printf("cnt = %d\n",cnt);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is child fork,pid is %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3)
                        {
                                exit(0);//正常退出
                        }
                }
        }
        return 0;
}

一篇文章了解Linux下进程相关知识_第13张图片
*这里为什么父进程要等待子进程退出?父进程给子进程交代任务,不管干没干完什么退出情况都要给父进程一个退出码,要调用WEXITSTATUS(status)函数对退出码进行解析。
wait函数的三大功能:
(1)如果其所有子进程都还在运行,则阻塞。
(2)如果一个子进程已终止,正等待父进程收集其状态则取得该子进程终止状态并立即返回。
(3)如果它没有任何子进程则立即返回出错。
wait使调用者阻塞(即父进程不运行等待子进程结束运行),waitpid有一个选项可以使调用者不阻塞。
pid_t waitpid(pid_t pid, int status, int options);
第一个参数:进程的pid号,在父进程调用,这个pid即为子进程pid号。
第二个参数:子进程退出状态存到该地址里
第三个参数:多用WNOHANG

#include 
#include 
#include 
#include 
#include 
int main()
{
        pid_t pid;
        int status = 100;
        int cnt = 0;
        pid = fork();
        if(pid>0)
        {
                waitpid(pid,&status,WNOHANG);//pid就是子进程pid
                while(1)
                {
                        printf("the status is %d\n",WEXITSTATUS(status));
                        printf("this is father fork,pid is %d\n",getpid());
                        sleep(1);
                        printf("cnt = %d\n",cnt);
                }
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is child fork,pid is %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3)
                        {
                                exit(0);//正常退出
                        }
                }
        }
        return 0;
}

一篇文章了解Linux下进程相关知识_第14张图片
这里我们让父子进程一起运行,用到waitpid这个api,那这个子进程就变成了僵尸进程。

#include 
#include 
#include 
#include 
#include 
int main()
{
        pid_t pid;
        pid = fork();
        int cnt = 0;
        if(pid>0)
        {
                printf("this is father fork,pid is %d\n",getpid());
                sleep(1);
        }
        else if(pid == 0)
        {
                while(1)
                {
                        printf("this is child fork pid is %d ,my father pid is %d\n",getpid(),getppid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3)
                        {
                                exit(0);//正常退出
                        }
                }
        }
        return 0;
}
//getppid()获取父进程号

一篇文章了解Linux下进程相关知识_第15张图片
首先父进程先运行,子进程与父进程同时运行,过一会父进程退出,此时子进程变为孤儿进程,系统默认把init进程作为子进程的父进程(init进程默认pid为1)。
孤儿进程:父进程如果不等待子进程退出,在子进程结束之前就结束自己的生命,此时子进程叫做孤儿进程,Linux系统避免存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

exec族函数作用:在调用一个进程中,转而调用另一个进程。

这段代码我们在自己写cp指令时用到过,作用是打印出命令行所有指令。


#include 
 
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;

一篇文章了解Linux下进程相关知识_第16张图片
当execl调用失败后,返回-1,并接着未执行的程序往下执行。执行成功的话不往下运行。
perror(“why”); //如果execl返回出错,返回了一个error,可以被这perror( )解析出来。

#include 
#include 
#include 
//函数原型: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");     //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 
    }
    printf("after execl\n");
    return 0;
}

在这里插入图片描述
ls是否也可被执行呢?我们用whereis ls查看ls绝对路径。

#include 
#include 
#include 
//函数原型: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");     //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 
    }
    printf("after execl\n");
    return 0;
}              

一篇文章了解Linux下进程相关知识_第17张图片

这里我们让excel运行ls程序,我们查看ls的绝对路径,用whereis ls,用族函数打开与用命令行打开运行的结果是一样的。如果要实现ls -l呢,很简单,在excel后面给ls传参即可,第三个参数变为“-l”即可。
同样的操作我们来查看系统时间:

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("this pro system date:\n");
    if(execl("/bin/date","date",NULL,NULL) == -1)
    {
        printf("execl failed!\n");

        perror("why");     //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 
    }
    printf("after execl\n");
    return 0;
}

同样的步骤,先用whereis date查看date的绝对路径,其次date不需要其他参数,第三个参数为NULL,运行该程序,execl自动调用date函数。
一篇文章了解Linux下进程相关知识_第18张图片
那么每次都需要通过whereis来查找变量绝对路径是不是太麻烦呢,我们可以使用函数,execlp通过环境变量来查找可执行文件。

#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("this pro system date:\n");
    if(execlp("date","date",NULL,NULL) == -1)
    {
        printf("execl failed!\n");

        perror("why");     //如果execl返回出错,返回了一个error,可以被这perror( )解析出来 
    }
    printf("after execl\n");
    return 0;
}

通过环境变量自动找到date命令。
在这里插入图片描述
fork和exce一起使用:这里我们用检测输入的方式调用子进程,创建好shell脚本,作用为改变文本配置,子进程为调用exce函数,通过这个函数来调用changedata脚本。

#include 
#include 
#include 
int main()
{
        pid_t pid;
        int data;
        while(1)//父进程一直存在检测用户输入
        {
                printf("please input a numner\n");
                scanf("%d",&data);
                if(data == 1)//客户端有输入
                {
                        pid = fork();//来一个客户则创建一个子进程来执行请求
                        if(pid>0)
                        {

                        }
                        else if(pid == 0)//子进程运行,服务客户端
                        {
                                execl("./changedata","changedata","config.txt",NULL);
                        }
                }
                else
                {
                        printf("wait,do nothing\n");
                }
        }
        return 0;
}
~   

changedata为:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc,char* argv[2])
{
        char* readbuf;
        int fdScr;

        if(argc != 2)//命令行必须输入两个操作命令
        {
                printf("input error\n");
                exit(-1);
        }

        fdScr = open(argv[1],O_RDWR);
        int size = lseek(fdScr,0,SEEK_END);//lseek反应的是文件偏移量
        lseek(fdScr,0,SEEK_SET);//光标重新移到文件头
        readbuf = (char*)malloc(sizeof(char)*size);//而readbuf需要开辟(偏移量*字节数)
        read(fdScr,readbuf,size*sizeof(char));//把目标文件读到readbuf里面

        char* p = strstr(readbuf,"LENG=");//在目标文件里面查找“LENG=”字符
        if(p == NULL)
        {
                printf("Not Found\n");
                exit(-1);
        }
        p = p + strlen("LENG=");//如果找到该字符串,则strstr函数返回该字符串开始的位置,我们让该字符串偏移到我们想要的位置
        *p = '9';//修改我们需要的文件配置,以上均是把静态数据区的文件复制到动态数据区,修改以后的值是在动态数据区的readbuf,文件里面存的都是字>符
        lseek(fdScr,0,SEEK_SET);//光标移到文件头
        write(fdScr,readbuf,strlen(readbuf));//把readbuf里面的值重新写到该文件从头覆盖
        close(fdScr);//关闭该文件,并将数据同步更新到源文件
        return 0;
}

system函数:
调用成功则返回进程的状态值;不能执行则返回127,失败返回-1.
作用也都是执行一个shell指令。当检测到子进程运行时,我们只需要让系统执行system函数即可。

system("./changedata config.txt");

popen函数:比system函数的好处是:system函数是把调用的结果直接打印到终端页面,popen函数把调用结果返回一个流,我们可以调用read函数读取这个流把结果读取出来。

#include 
#include 

int main()
{
	FILE* fp;
	char ret[1024];
	fp = popen("ps","r");
	int n_read = fread(ret,1,1024,fp);//一次读一个字节,读1024次
	printf("read %d byte,context is %s\n",n_read,ret);
    return 0;
}

一篇文章了解Linux下进程相关知识_第19张图片

你可能感兴趣的:(Linux,linux)