Linux系统编程学习笔记一

  1. 进程相关概念
  2. fork/getpid/getppid函数的使用
  3. ps/kill命令的使用
  4. vfork, execl/execlp函数的使用
  5. 孤儿进程和僵尸进程
  6. wait函数的使用
  7. waitpid函数的使用

一、进程的相关概念

1、程序和进程

  • 程序:二进制文件,占用磁盘空间
  • 进程:启动的程序
    • 所有的数据都在内存
    • 需要占用更多的系统资源,如cpu,物理内存
    • 程序(代码)删除后,正在运行的程序(进程)可以正常执行

2、并行和并发

  • 并行:多核处理机或多处理机在同一时刻同时执行多个进程。
  • 并发:单核处理机在一个时间间隔内按时间片轮转的方式执行多个进程。
    名词参考:多处理机、多核cpu、多线程cpu的区别

3、PCB
       进程控制块PCB实际上是task_struct结构体,其中主要的内部成员有:

  • 进程id
  • 进程状态 kill -9 pid
  • 用于保存和恢复的cpu寄存器
  • 虚拟地址空间信息
  • 描述控制终端的信息
  • 当前工作目录
  • umask掩码(用于给文件设置权限)
  • 文件描述符表
  • 用户id和组id stat filename
  • 会话(session)和进程组(进程和子进程构成)
  • 进程可以使用的资源上限 ulimit -a

4、进程的五种状态
在这里插入图片描述

二、fork/getpid/getppid函数的使用

fork()函数

  • 返回值:父进程的返回值是子进程的pid(正值),子进程的返回值是0。负值表示创建失败。
  • 父子进程的执行顺序随机,看谁先抢占cpu
  • 子进程创建成功后,地址空间同父进程一样,只是进程id不同。父子进程代码相同,现阶段的执行状态相同,下一步都会运行fork函数之后的代码。

在这里插入图片描述
实例1:循环创建子进程

   #include <stdio.h>
   #include <unistd.h>
   #include <stdlib.h>
   #include <string.h>
   #include <sys/stat.h> 
   #include <sys/types.h>
   int main(int argc, const char* argv[])
   {
    
                 
       pid_t pid;
      for(int i=0; i<3; i++)       
          pid=fork();        
      return 0;                                                                          
  }  

结果:在这里插入图片描述
实例2:循环创建多个子进程,且这些子进程必须是兄弟关系

   #include <stdio.h>
   #include <unistd.h>
   #include <stdlib.h>
   #include <string.h>
   #include <sys/stat.h>
   #include <sys/types.h>
   int main(int argc, const char* argv[])
   {
    
             
       int i;                                                                             
      pid_t pid;
      for(i=0; i<3; i++)
      {
    
         
          pid=fork();
          if(pid==0)//只允许原始的父进程创建子进程
             break;
      }     
      if(i==0)
          printf("first process,pid=%d\n",getpid());
      else if(i==1)
          printf("second process,pid=%d\n",getpid());
      else if(i==2)
          printf("third process,pid=%d\n",getpid());
      else//最后退出for循环的是父进程  
    	{
    
    
 			sleep(1);  //使父进程等待所有子进程执行完毕再退出
    		printf("parent process,pid=%d\n",getpid());
      }
 		return 0;
 }         
运行结果:
first process,pid=379
second process,pid=380
third process,pid=381
parent process,pid=378

三、ps/kill命令

ps: 查看当前进程

  • ps aux | grep ./helloworld 将ps命令输出重定向到管道,grep从管道读入数据,查找指定内容。第一行第二列是进程pid
  • ps ajx | grep ./helloworld 查找结果中,第一行第一列是父进程id(ppid),第二列是进程pid,第三列是进程组id,第四列是会话id(sid)

kill:发信号给指定进程

  • kill -l 列出所有信号
    在这里插入图片描述
  • kill -9 pid 或 kill -SIGKILL pid     无条件杀死某个进程,如结束一个死循环。

进程间的数据共享
在这里插入图片描述
fork()出子进程之后,

  • 后续各自互不影响,之后可各自进行不同的操作
  • 父子进程间的数据在进行写操作之前数据是共享的:读时共享,写时复制,谁需要改动数据就先复制一份再改动
  • 父子进程不能使用全局共享变量通信,因为父或子进程中数据的改变不会对对方产生影响

四、vfork, execl/execlp函数的使用

exec函数簇

  • 使父子进程执行两种不相干的操作
  • 能够替换进程地址空间中的源代码(.txt段)
  • 当前程序中调用另外一个应用程序,首先想到exec之前需要fork
  • 返回值:如果函数执行成功切换到另一个程序,不会再执行之后的代码,不返回;如果函数执行失败,返回-1,并打印错误信息

execl

  • 原型:int execl(const char* path, const char* arg, …., NULL);
  • path:调用的进程的路径,一般是自己写的程序,系统程序也可
  • arg:变参,是一个或多个参数
    • 第一个arg: 占位参数,无意义(一般写调用程序的名称)
    • 后面的arg:命令的参数(没有可以不写)
    • 最后是NULL,表示没有参数了

示例:

/*父进程创建出子进程后,子进程把从父进程拷贝过来的代码换成系统程序ls,从而在运行子进程时相当于是敲了ls*/
#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, const char* argv[])
{
    
               
     int i;       
     for(i=0; i<4; i++)                                                              
            printf("parent i =%d\n",i);
     pid_t pid=fork();
     if(pid==0)
     	   execl("/bin/ls","ls",NULL);
	  for(i=0; i<3; i++)   
   		printf("+++++++++++ i=%d\n",i);
		return 0;  
}       
运行结果:
parent i =0
parent i =1
parent i =2
parent i =3
+++++++++++ i=0
+++++++++++ i=1
+++++++++++ i=2
a.c    Code     golang  myhello.c     package.json  webpack.config.js                      
a.out  Desktoplibnode_modules  src-gen    yarn.lock

execlp

  • 原型:int execlp(const char* file, const char* arg, …., NULL);
  • file:执行的系统命令的名字(一般用于执行系统自带的程序,若要执行自定义程序,需指定绝对路径)
  • arg:变参,是一个或多个参数
    • 第一个arg: 占位参数,无意义(一般写调用程序的名称)
    • 后面的arg:命令的参数(没有可以不写)
    • 最后是NULL,表示没有参数了
/*父进程创建出子进程后,子进程把从父进程拷贝过来的代码换成系统程序ps,同时传参数aux,从而在运行子进程时相当于是敲了ps aux*/
#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, const char* argv[])
{
    
               
     int i;       
     for(i=0; i<4; i++)                                                              
           printf("parent i =%d\n",i);
     pid_t pid=fork();
     if(pid==0)
     {
    
    
     	   execlp("ps","ps","aux",NULL); 
     	   perror("execlp");
     }
	  for(i=0; i<3; i++)   
   		   printf("+++++++++++ i=%d\n",i);
	  return 0;  
}       

执行结果:
在这里插入图片描述
如果把要调用的功能换成其他系统中不存在的功能,结果会是:

parent i =0
parent i =1
parent i =2
parent i =3
+++++++++++ i=0
+++++++++++ i=1
+++++++++++ i=2
execlp: No such file or directory                                                          
+++++++++++ i=0
+++++++++++ i=1
+++++++++++ i=2

孤儿进程和僵尸进程

孤儿进程:父进程在子进程没有结束前结束,子进程被称为孤儿进程,孤儿进程被init进程领养。子进程之所以必须有一个父进程,是因为子进程在没有父亲的情况下结束时,只能释放用户区空间,而不能释放pcb所占空间,pcb所占空间必须由父进程释放。

 #include <stdio.h>                      
 #include <unistd.h>                     
 #include <stdlib.h>                     
 #include <string.h>                     
 #include <sys/stat.h>                   
 #include <sys/types.h>                  
  /*孤儿进程*/
 int main(int argc, const char* argv[])
 {
    
    
    pid_t pid;
    pid=fork();
    if(pid==0){
    
    //子进程醒来时,父进程已结束,子进程成为孤儿进程
        sleep(1);
        printf("child pid=%d,ppid=%d\n",getpid(),getppid());
 	} 
    if(pid>0){
    
    
        printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
    }        
     return 0;
}  


由结果可见:child的ppid与parent的pid不同

僵尸进程:子进程执行完毕,父进程没有释放子进程的pcb,则子进程变为僵尸进程。僵尸进程无法运行却仍占用一部分空间。

  • kil-9用来杀死“活”的进程,无法杀死僵尸进程,可以通过杀死父进程的方法杀死僵尸进程
  • 原理:僵尸进程的父进程一旦被杀死,init进程就会来领养僵尸进程,从而释放僵尸进程的pcb所占的空间。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
/*僵尸进程*/
int main(int argc, const char* argv[])
{
    
    	
	pid_t pid;
	pid=fork();
	if(pid==0){
    
    
		printf("child pid=%d,ppid=%d\n",getpid(),getppid());
	}
	if(pid>0){
    
    
		while(1){
    
    //父进程一直在死循环,无暇释放子进程pcb,子进程成为僵尸进程
			printf("====parent\n");
			printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
		}
	}
	return 0;
}

进程回收

wait-阻塞函数,在子进程运行结束后回收子进程

  • 所需头文件:#include <sys/wait.h>
  • 函数原型:pid_t wait(int *statu);
  • 返回值
    • -1:回收失败,已经没有子进程了
    • >0:回收的子进程对应的pid
  • 参数:status 判断进程是如何死的
    • 正常退出
    • 被某个信号杀死
  • 调用一次只能回收一个子进程
  • 查看参数
    • WIFEXITED(status);//非0说明进程正常返回或退出 return ,exit
    • WEXITSTATUS(status); //获取正常返回或退出时的返回值,返回值就是return后面的值或者exit函数的参数
    • WIFSIGNALED(status); //为非0,程序异常终止,被信号杀死
    • WTERMSIG(status);//获取进程被哪个信号杀死
//不查看子进程退出状态
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
/*wait回收子进程*/
int main(int argc, const char* argv[])
{
    
    
	pid_t pid;
	pid=fork();
	if(pid==0)
	{
    
    
		sleep(2);
		printf("child pid=%d,ppid=%d\n",getpid(),getppid());
	}
	if(pid>0)
	{
    
    
		printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
		//回收子进程
		pid_t wpid=wait(NULL);//不想查看退出状态,传入NULL
		printf("died child pid=%d\n",wpid);
	}
	for(int i=0;i<4;++i)
		printf("i=%d\n",i);
	return 0;
}

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

//程序正常退出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
/*wait回收子进程*/
int main(int argc, const char* argv[])
{
    
    
	pid_t pid;
	pid=fork();
	if(pid==0)
	{
    
    
		sleep(2);
		printf("child pid=%d,ppid=%d\n",getpid(),getppid());
	}
	if(pid>0)
	{
    
    
		printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
		//回收子进程
		int status;
		pid_t wpid=wait(&status);//status获取的是子进程的返回值
		if( WIFEXITED(status) )
			printf("程序正常退出,返回值为 %d\n",WEXITSTATUS(status));
		if( WIFSIGNALED(status) )
			printf("程序异常退出,杀掉进程的信号量为 %d\n",WTERMSIG(status));
		printf("died child pid=%d\n",wpid);
	}
	for(int i=0;i<4;++i)
		printf("i=%d\n",i);
	return 9;
}

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

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
/*wait回收子进程*/
int main(int argc, const char* argv[])
{
    
    
	pid_t pid;
	pid=fork();
	if(pid==0)
	{
    
    
		while(1)
		{
    
    
			sleep(2);
			printf("child pid=%d,ppid=%d\n",getpid(),getppid());
		}
	}
	if(pid>0)
	{
    
    
		printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
		//回收子进程
		int status;
		pid_t wpid=wait(&status);//status获取的是子进程的返回值
		if( WIFEXITED(status) )
			printf("程序正常退出,返回值为 %d\n",WEXITSTATUS(status));
		if( WIFSIGNALED(status) )
			printf("程序异常退出,杀掉进程的信号量为 %d\n",WTERMSIG(status));
		printf("died child pid=%d\n",wpid);
	}
	for(int i=0;i<4;++i)
		printf("i=%d\n",i);
	return 0;
}

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

  • 原型:pid_t waitpid(pid_t pid, int *status, int options);
  • 参数:
    • pid
      • pid >0 等待回收进程id与pid相等的子进程
      • pid==-1 等待回收任一个子进程,等同于wait(包括被过继到其他进程组的子进程)
      • pid==0 等待回收当前组中的所有子进程(等待组ID等于调用进程的组ID的任一进程)
      • pid<-1 等待回收原来属于当前进程组,但是现在被过继到其他进程组的子进程
    • status:返回子进程退出的状态,同wait函数的status
    • options:设置为WNOHANG,函数非阻塞;设置为0,函数阻塞
  • 返回值
    • >0,返回清理程序的子进程ID
    • -1,无子进程
    • =0,参数options为WNOHANG(非阻塞),且子进程正在运行

练习:
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
/*操作终端文件,/dev/tty默认阻塞*/
int main(int argc, const char* argv[])
{
    
    	
	//如果有名为temp的文件就以可读写方式打开,否则就创建一个
	int fd = open("temp",  O_CREAT | O_RDWR, 0664)if(fd == -1)
	{
    
    
		perror("open error");
		exit(1);
	}
	pid_t pid=fork();//父子进程都可以对已打开的文件进行读写操作
	if(pid==-1)
	{
    
    
		perror("fork error");
		exit(1);
	}
	if(pid>0)
	{
    
    
		char* p="近期,微软向Linux开发人员伸出了橄榄枝,希望Linux开发人员能够转移到Windows 10平台上进行开发";
		write(fd, p, strlen(p)+1);
		close(fd);
	}
	else if(pid == 0)
	{
    
    
		sleep(1);//子进程睡醒之后,父进程已经把信息写入文件
		char buf[1024];
		lseek(fd, 0, SEEK_SET);//虽然父进程已经close了文件,但则只意味着文件对于父进程来说关闭了,对子进程来说仍打开着,文件指针在末尾,这里将文件指针移动到文件起始位置
		int len = read(fd, buf, sizeof(buf));//将文件内容读到buf数组
		printf("%s\n", buf);
		close(fd);
	}
	return 0;
}

延迟1s后显示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char* argv[])
{
    
    	
	int num = 3;
	int i = 0;
	pid_t pid;
	for(i=0; i<num; i++)//创建三个子进程,并且组织子进程再创建孙子进程
	{
    
    
		pid=fork();
		if(pid==0)
			break;
	}
	if(i == 0)
	{
    
    
		execlp("ps", "ps", "aux", NULL);
		perror("execlp ps");
		exit(1);
	}
	else if(i == 1)
	{
    
    
		execl("/home/shiyanlou/hello", "hello", NULL);
		perror("execl hello");
		exit(1);
	}
	else if(i == 2)
	{
    
    
		execl("./error", "error", NULL);
		perror("execl error");
		exit(1);
	}
	else if(i == num)
	{
    
    
		int status;
		pid_t wpid;
		while((wpid = waitpid(&status)) != -1)//父进程没有回收完子进程就继续回收
		{
    
    
			printf(" ----- child died pid = %d\n", wpid);
			if( WIFEXITED(status) )
				printf("return value %d\n",WEXITSTATUS(status));
			else if( WIFSIGNALED(status) )
				printf("deid by signal: %d\n",WTERMSIG(status));
		}
	}
	return 0;
}

使用waitpid时的等效代码(只换while部分):

while((wpid = waitpid(-1, &status, WNOHANG)) != -1)//父进程没有回收完子进程就继续回收
		{
    
    //options:设置为WNOHANG,函数非阻塞,就会一直检测子进程有没有结束,若不结束就会返回0,这里设置如果不结束就不执行下面的代码,而是继续循环
			if(wpid == 0)
				continue;
			printf(" ----- child died pid = %d\n", wpid);
			if( WIFEXITED(status) )
				printf("return value %d\n",WEXITSTATUS(status));
			else if( WIFSIGNALED(status) )
				printf("deid by signal: %d\n",WTERMSIG(status));
		}

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

猜你喜欢

转载自blog.csdn.net/qq_44378854/article/details/106597663