Linux系统编程——进程

什么是程序,什么是进程,有什么区别

  1. 程序是静态的概念,gcc xxx.c -o pro磁盘中生成pro文件叫做程序
  2. 进程是程序的一次运行活动,通俗点意思就是程序跑起来了,系统中就多了一个进程
  3. 区别就是是否运行了起来

如何查看系统中有哪些进程

  1. 使用ps指令查看,详细的查看需要配合grep指令,ps -aux | grep
  2. 使用top指令查看,类似于win任务管理器

什么是进程标识符

  1. 每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
    1. Pid=0:交换进程,作用是进程调度
    2. Pid=1:init进程,作用是系统初始化
  2. 编程调用getpid()函数获取自身的进程标识符
  3. getppid()获取父进程的进程标识符

什么叫父进程,什么叫子进程

假设:进程A创建了进程B
则 A叫做父进程,B叫做子进程,父子进程是相对概念,理解为人类中的父子关系

C程序的存储空间是如何分配的

image.png
image.png

fork()函数

创建一个子进程,同时将父进程的许多参数复制给子进程
int a=fork():这段表达式,如果在父进程中a=子进程的pid,如果在子进程中a=0

fork()小应用:通过scanf()阻塞主进程等待输入,如果输入1,那么创建一个子进程,子进程不断的执行相应的while

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main()
{	
	
	pid_t pid;
	
	pid = getpid();
	int data=0;	

	while(1){
		printf("current process id:%d\n",pid);
		printf("scanf 1 creat process\n");
		scanf("%d",&data);

		if(data==1){
			pid = fork();
			if(pid==0){
				while(1){
					printf("child pid=%d\n",getpid());
					sleep(3);
				}
			}
		}else {
			printf("wait,do nothing");
		}
	}
	return 0;
}

小问题:

int main()
{

        pid_t pid;

        pid = getpid();
        printf("fork=======\n");//注意这里
        int fork_pid = fork();
        printf("pid:%d, getpid=%d, fork_pid=%d\n",pid,getpid(),fork_pid);

        return 0;
}

int main()
{

        pid_t pid;

        pid = getpid();
        printf("fork=======");//注意这里
        int fork_pid = fork();
        printf("pid:%d, getpid=%d, fork_pid=%d\n",pid,getpid(),fork_pid);

        return 0;
}

这两段代码的不同之处只是printf("fork=======");printf("fork=======\n");的区别,但是最后的输出结果是不同的,

  • printf("fork=======")

image.png

  • printf("fork=======\n")

image.png
原因在于(AI真方便)
image.png
换句话说当子进程创建后,子进程会从被创建的行开始执行,并不是从第一行开始执行,而子进程创建之后,会将父进程的各种状态复制一遍,包括了缓冲区。由于printf("fork=======")满足了不刷新缓冲区的条件,所以不会立即刷新缓冲区,所以此时缓冲区内保存着一份fork=======,当子进程创建后,子进程的缓冲区内也保存了一份来自于父进程的fork=======,但这两者的缓冲区是独立的,所以才出现了上面输出的不同
image.png

vfork()创建进程

与fork()的不同

  1. vfork()直接使用父进程存储空间,不拷贝
  2. vfork()保证子进程先运行,当子进程调用exit()推出后,父进程才执行

进程退出

正常退出

  1. main()调用return
  2. 进程调用exit():标准C库,会去先对缓冲区做一些处理再退出
  3. 进程调用_exit()或者_Exit(),属于系统调用:不会对缓冲区进行处理
    1. _exit()_Exit()是对exit()的封装

image.png

  1. 进程最后一个线程返回
  2. 最后一个线程调用pthread_exit

异常退出

  1. 调用abort()
  2. 当进程收到某些信号时,如ctrl+c
  3. 最后一个线程对取消(cancellation)请求做出响应
    :::info
    不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符。释放它所使用的存储器等。
    对于上述任意一种种植情况,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit,_exit,_Exit),实现这一点的方法是,将其退出状态左为参数传递给函数,在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都能用wait()或waitpid()取得其终止状态
    :::

等待子进程退出

父进程等待进程退出并收集进程的退出状态

image.png

如果不收集进程的退出状态,那么子进程就会变成僵尸进程

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

int main()
{	
	
	pid_t pid;
	int data=0;
	pid = vfork();
	if(pid>0){
		while(1){
			printf("data=%d\n",data);
			printf("current father pid=%d\n",getpid());
			sleep(1);
		}
	}else if(pid==0){
		while(1){
			printf("current chilid printf:pid=%d\n",getpid());
			sleep(1);
			data++;
			if(data==3){
				exit(0);
			}

		}
	}
	return 0;
}

等待3s后,通过命令:ps -aux | grep a.out查看如下

image.png

如何收集进程的退出状态

  1. wait(int* status):如果其所有子进程都还在运行,则阻塞
    1. status参数为一个整型数指针
      1. 如果非空:子进程退出状态放在它所指向的地址中
      2. 空:不关心退出状态
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{	
	
	pid_t pid;
	int data=0;
	int status=10;

	pid = vfork();
	if(pid>0){
		wait(&status);//阻塞等待
		printf("status:%d\n",WEXITSTATUS(status));
		while(1){
			printf("data=%d\n",data);
			printf("current father pid=%d\n",getpid());
			sleep(1);
		}
	}else if(pid==0){
		while(1){
			printf("current chilid printf:pid=%d\n",getpid());
			sleep(1);
			data++;
			if(data==3){
				exit(100);
			}

		}
	}
	return 0;
}
  1. waitpid(pid_t pid, int* status, int options):如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
    1. 参数解析:status和wait()中的status一样

image.png
对于pid参数,其等待的ID可以通过vfork()的返回值得到

int main()
{

        pid_t pid;
        int data=0;
        int status=10;

        pid = fork();//1
    	//pid=vfork();
        if(pid>0){
                printf("father vfork_pid=%d\n",pid);
                waitpid(pid,&status,WNOHANG);//2
                printf("status:%d\n",WEXITSTATUS(status));
                while(1){
                        printf("data=%d\n",data);
                        printf("current father pid=%d\n",getpid());
                        sleep(1);
                }
        }else if(pid==0){
                while(1){
                        printf("pid=%d\n",pid);
                        printf("current chilid printf:pid=%d\n",getpid());
                        sleep(1);
                        data++;
                        if(data==5){
                                exit(100);
                        }

                }
        }
        return 0;
}
  1. 如果1处调用的是pid=fork(),2处调用的是waitpid(pid,&status,WNOHANG);,那么程序实际上会并行运行,但依然会造成僵死进程,这里我猜测应该是父进程运行到waitpid()时,子进程并没有退出,waitpid()无法收集,运行过去后,子进程运行完毕,但是父进程已经进入了while(),所以子进程变为了僵死进程

  2. 如果1处调用的是pid=vfork(),2处调用的不变,程序实际上并不会并行运行,依然会阻塞运行,但不会造成僵死进程

  3. waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options):如果它没有任何子进程,则立即出错返回


孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己,此时子进程叫做孤儿进程,Linux避免系统存在过多的孤儿进程,init进程收留孤儿进程,变成孤儿进程的夫进程

exec族函数

//主进程文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{	
	pid_t pid;
	pid = getpid();
	int data=0;	
	while(1){
		printf("current process id:%d\n",pid);
		printf("scanf 1 creat process\n");
		scanf("%d",&data);
		if(data==1){
			pid = fork();
			if(pid==0){
				if(execl("./echoarg","./echoarg","TEST.config",NULL) == -1){
                			printf("execl failed\n");
               				perror("why");
       				}
        			printf("after execl\n");
			}
			
		}else {
			printf("wait,do nothing");
		}
	}
	return 0;
}
#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)
{
	printf("%s\n",argv[0]);
	printf("%s\n",argv[1]);
	//read src
	int fds_src = open(argv[1],O_RDWR);
	int fds_src_size = lseek(fds_src,0,SEEK_END);
	//move head
	lseek(fds_src,0,SEEK_SET);
	char* buf = (char*)malloc(sizeof(char)*fds_src_size);
	read(fds_src,buf,fds_src_size);
	lseek(fds_src,0,SEEK_SET);
	char* p = strstr(buf,"two=");
	p=p+strlen("two=");
	*p='8';
	write(fds_src,buf,fds_src_size);
	close(fds_src);	
	return 0;
}

system()函数

实际上system()要比exec族函数要简单粗暴许多

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main()
{	
	pid_t pid;
	pid = getpid();
	int data=0;	
	while(1){
		printf("current process id:%d\n",pid);
		printf("scanf 1 creat process\n");
		scanf("%d",&data);

		if(data==1){
			pid = fork();
			if(pid==0){
				if(system("./echoarg TEST.config") == -1){
                			printf("execl failed\n");
               				perror("why");
       				}
        			printf("after execl\n");
			}
		}else {
			printf("wait,do nothing");
		}
	}
	return 0;
}

在system()中只需要这样调用system("./echoarg TEST.config")=execl("./echoarg","./echoarg","TEST.config",NULL)

popen():获取运行的输出结果

FILE *popen(const char *command, const char *type);:第一个参数是指令,第二个参数是指定是读取还是写入模式。如果是r模式,该函数会将第一个参数的结果放到管道中,然后返回一个流,通过FILE流将数据读取出来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值