Linux系统进程编程

 回顾并记录,进程的知识点和代码。

 1.进程相关概念

    五个问题:

  (1)什么是程序,什么是进程,有什么区别?    

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

  (3)什么是进程标识符 

  (4)什么叫父进程,什么叫子进程. 

  (5)C程序的存储空间是如何分配的.    

     (1)程序是静态的概念,例如我们创建:gcc xxx.c -o pro.磁盘中生成pro文件,就叫程序.进程就是程序的一次运动,简单点就是跑起来的程序。

     (2)使用ps指令查看进程,实际中还可以使用grep来查找某一类或某个进程。top指令类似window的任务管理器。

     (3)每个进程都有一个非负整数表示唯一ID,叫做pid,类似身份证.getpid函数获取自身的进程表示符,getppid获取父进程的进程标识符。

     (4)进程A创建了进程B,那么A叫父进程,B叫子进程。

     (5) 解释一下,正文就是代码段,初始化的数据通俗来讲就是赋了值的变量,那么未初始化就是未赋值的变量。堆一般是动态开辟时申请空间,栈,函数段的地址空间。

在这里插入图片描述

2.fork函数创建进程

   pid_t fork(void);

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

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


int main()
{
   pid_t pid;
   pid_t pid2;
// pid_t getpid(void);
    pid = getpid();
    printf("before fork: pid = %d\n",pid);
    
   
    fork();

   pid2 = getpid();
   printf("after fork: pid = %d\n",pid2);

   if(pid == pid2)
   {
        printf("the father pid\n"); 
   }
   else
   { 
        printf("the child pid is :%d\n",getpid()); 
   }
    return 0;
}

   关于父子进程间存储空间的分配。在早期的Linux中,子进程完整拷贝父进程的存储空间。随着技术的发展。目前是写实拷贝,即如果后面不对数据做出改变,那么采用共享的方式。且子进程改变数据,不影响父进程。如下代码段,打印的data并不相同。

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


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


   if(pid > 0 )
   {
        printf("the father pid is :%d\n",getpid()); 
   }
   else if(pid == 0)
   { 
        printf("the child pid is :%d\n",getpid()); 
        data = data+10;
   }

   printf("%d\n",data);
    return 0;
}

 3.fork创建子进程的目的

    (1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此要求。父进程则继续等待下一个服务请求的到达。

    (2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。(讲解到exec再进行演示)

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


int main()
{
   pid_t pid;
// pid_t getpid(void);
   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;
}

  4.vfork创建进程

     vfork和fork的区别:1.vfork直接使用父进程存储空间,不拷贝。也就是说,vfork中改变某一变量的值,那么父进程也会改变。2.vfork保证子进程先运行,当子进程调用exit退出后父进程才执行.

  5.进程退出

     不管进程如何终止,最后都会执行内核中同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。     

      对上述任意一种终止情形,我们希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit、_Exit),实现这一点的方法是,将退出状态作为参数传送给函数。在异常终止情况下,内核产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。(子进程退出状态不被收集,会变成僵尸进程)

 status参数:是一个整形指针。非空:子进程退出状态放在它所指向的地址。空:不关心退出状态。

检查wait和waitpid所返回得终止状态的宏的使用方法展示:

   

#include <stdlib.h> 
#include <sys/wait.h>


int main()
{
   pid_t pid;
   pid_t pid2;
// pid_t getpid(void);
   int cnt = 0;   
   int status = 10; 
  pid = fork();
  

   if(pid > 0 )
   {
        pid2 = wait(&status);
        printf("%d\n",pid2);
        printf("status is :%d\n",WEXITSTATUS(status));
        while(1)
       {
          printf("the father pid is :%d\n",getpid()); 
          sleep(3);
          printf("cnt = %d\n",cnt);
       }
   }
   else if(pid == 0)
   {   
        while(1)
       {
          printf("the child pid is :%d\n",getpid()); 
          sleep(3);
          cnt++;
          if(cnt == 5)
          {
             exit(3);
          }
       }
   }

    return 0;
}

  相关函数:pid_t waitpid(pid_t pid, int *status, int options).wait使调用者阻塞,waitpid有一个选项可以使调用者不阻塞。(不过在实践中发现,这种不阻塞的形式,子进程为僵尸进程)

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


int main()
{
   pid_t pid;
// pid_t getpid(void);
   int cnt = 0;   
   int status = 10; 
  pid = fork();
  

   if(pid > 0 )
   {
  //      wait(&status);
//        pid_t waitpid(pid_t pid, int *status, int options);
        waitpid(pid,&status,WNOHANG);
        printf("status is :%d\n",WEXITSTATUS(status));
        while(1)
       {
          printf("the father pid is :%d\n",getpid()); 
          sleep(3);
          printf("cnt = %d\n",cnt);
       }
   }
   else if(pid == 0)
   {   
        while(1)
       {
          printf("the child pid is :%d\n",getpid()); 
          sleep(3);
          cnt++;
          if(cnt == 5)
          {
             exit(3);
          }
       }
   }

    return 0;
}

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

  6.exec族函数

    我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。

    函数族以及原型:

    返回值:exec族函数的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原   程序的调用点接着往下执行。

    参数说明:
    path:可执行文件的路径名字
    arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
    file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

     exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
     l : 使用参数列表
     p:使用文件名,并从PATH环境进行寻找可执行文件
     v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
     e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  printf("before execl\n");
// int execl(const char *path, const char *arg, ...);
  if(execl("/bin/ls","ls","-l",NULL) == -1)
  {
     printf("execl failed!\n");
     perror("why");//解析错误原因
  }
  printf("after execl\n");
  return 0;  
}

   execlp函数:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  printf("before execl\n");
// int execl(const char *path, const char *arg, ...);
  if(execlp("date","date",NULL,NULL) == -1)
  {
     printf("execl failed!\n");
     perror("why");//解析错误原因
  }
  printf("after execl\n");
  return 0;  
}

   execvp函数:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  char *argv[]={"ps","-l",NULL};
  printf("before execl\n");
// int execl(const char *path, const char *arg, ...);
  if(execvp("ps",argv) == -1)
  {
     printf("execl failed!\n");
     perror("why");
  }
  printf("after execl\n");
  return 0;  
}

    execv函数:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  char *argv[]={"ps","-l",NULL};
  printf("before execl\n");
// int execl(const char *path, const char *arg, ...);
  if(execv("/bin/ps",argv) == -1)
  {
     printf("execl failed!\n");
     perror("why");
  }
  printf("after execl\n");
  return 0;  
}

   execle函数改变环境变量:    (1).将exec族函数配合fork使用

        我们把"修改文件内数据"的代码拿出来结合execl函数(程序取名changedata):

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


int main()
{
   pid_t pid;
// pid_t getpid(void);
   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)
         {
              char *readBuf=NULL;
			  char *p=NULL;


		  int fd_src = open("config.txt",O_RDWR);
		  int size = lseek(fd_src,0,SEEK_END);
		  lseek(fd_src,0,SEEK_SET);
		  readBuf = (char *)malloc(size*sizeof(char)+16);
		  read(fd_src,readBuf,size);
		  p = strstr(readBuf,"SCORE=");
 		  if(p == NULL)
 		  {
 		    printf("error\n");
  		    exit(-1);
 		  }
 		  p = p+strlen("SCORE=");
 	  	 *p = '7';
		  lseek(fd_src,0,SEEK_SET);
   		  write(fd_src,readBuf,strlen(readBuf));
	
   	 	  close(fd_src);
                  exit(0);
 
         }
    }
    else
    {
        printf("wait,do nothing\n");
    }



   }
    return 0;
}

  配合execl函数(由该进程到另一程序):

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


int main()
{
   pid_t pid;
// pid_t getpid(void);
   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)
         {
          execl("./changedata","changedata","config.txt",NULL);
         }
    }
    else
    {
        printf("wait,do nothing\n");
    }



   }
    return 0;
}

  7.system函数

     system函数简单来讲是关于execl函数的封装,用起来比execl更简便。

     system()函数的返回值如下:成功,则返回进程的状态值;当sh不能执行时,返回127;失败返回-1;

int main(void)
{
//  char *argv[]={"ps","-l",NULL};
  printf("before execl\n");
// int execl(const char *path, const char *arg, ...);
  if(system("ps") == -1)
  {
     printf("execl failed!\n");
     perror("why");
  }
  printf("after execl\n");
  return 0;  
}

   8.popen函数(可以获得运行的输出结果,使其不流失)

参数说明:

      command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。

  mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  FILE *fp; 
  char ret[1024]={0};
// int execl(const char *path, const char *arg, ...);
// FILE *popen(const char *command, const char *type);
  fp = popen("ps","r");
//   size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  int n_read = fread(ret,1,1024,fp);//放在字符串当中
  printf("read ret %d byte,ret = %s\n",n_read,ret);
  return 0;  
}


 

    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值