Linux进程控制

进程控制

1. fork后内核做什么?

  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构拷贝子进程
  3. 将子进程添加到系统进程列表中
  4. fork返回开始调度器调度

2. fork调用失败的原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过了上限

3. 进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

4. 查看进程退出码

echo $? 只会保留最近一次执行的进程的退出码

5. 进程退出常用方法

  1. main函数中return返回
  2. 调用exit()函数
  3. 调用_exit()函数

6. exit()函数和_exit()函数的区别

  1. exit()函数执行用户定义的清理函数
  2. 关闭所有打开的流,所有缓存器均被写入
  3. 调用_exit()函数

7. 进程的等待必要性

  1. 僵尸进程可能会造成内存泄漏
  2. 回收子进程资源,获取子进程退出信息

8. 进程等待方法

  1. wait()函数方法:成功返回等待进程PID,失败则返回-1;参数不关心则可以设置NULL
  2. waitpid()函数方法:成功返回子进程PID,如果设置了WNOHANG,而调用中waitpid已经收集返回0,调用出错返回-1;参数:PID=-1等待任一一个子进程,PID>0等待其进程PID与子进程相等的PID;status: WIFEXITED:查看进程是否退出,WEXITSTATUS:查看进程退出码:options:若指定PID的子进程没结束,返回0;结束返回子进程PID
  3. status:是int*类型,指向的是一个int大小的空间,32个比特位,其中从右向左数,第7-15这八个比特位是退出状态,第0-6这7个比特位是终止信号,第7这个比特位是core dump标志位

怎么获取进程的退出码和终止信号呢?

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    //子进程    
    int count = 0;    
    while(1)    
    {    
      if(count == 5)    
      {    
        break;    
      }    
      printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());    
      count++;                                                                                                                                                 
      sleep(1);    
    }    
    exit(2);    
  }    
  //父进程    
  int status = 0;    
  pid_t father_id = waitpid(id, &status, 0);    
  printf("我是父进程,PID:%d,PPID:%d,father_id:%d,子进程退出码:%d,子进程终止信号:%d\n", \    
      getpid(), getppid(), father_id, (status >> 8) & 0xFF, status & 0x7F);    
    
  return 0;    
}  

父进程在wait的时候,如果子进程没有退出,父进程是在干什么呢?

在子进程没有的时候,父进程只有一直在调用waitpid进程等待,这种等待就是阻塞等待。

如果父进程不想在waitpid处卡住呢,而是去做做别的事情呢,这时怎么来解决这个问题?

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    //子进程    
    int count = 0;    
    while(1)    
    {    
      if(count == 5)    
      {    
        break;    
      }    
      printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());    
      count++;    
      sleep(1);    
    }    
    exit(2);    
  }    
  //父进程    
  while(1)    
  {    
    int status = 0;    
    pid_t father_id = waitpid(id, &status, WNOHANG);    
    if(father_id < 0)    
    {    
      printf("err\n");    
      exit(-1);    
    }    
    else if(father_id == 0)                                                                                 
    {    
      printf("子进程没有退出\n");    
      sleep(1);    
      continue;    
    }    
    else    
    {    
      printf("我是父进程,PID:%d,PPID:%d,father_id:%d,子进程退出码:%d,子进程终止信号:%d\n", \    
          getpid(), getppid(), father_id, (status >> 8) & 0xFF, status & 0x7F);    
      break;    
    }    
    
    return 0;    
  }    
}    

也可以用宏来获取退出码

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    //子进程    
    int count = 0;    
    while(1)    
    {    
      if(count == 5)    
      {    
        break;    
      }    
      printf("我是子进程,PID:%d,PPPID:%d\n", getpid(), getppid());    
      count++;    
      sleep(1);    
    }    
    exit(2);    
  }    
  //父进程    
  while(1)    
  {    
    int status = 0;    
    pid_t father_id = waitpid(id, &status, WNOHANG);    
    if(father_id < 0)    
    {    
      printf("err\n");    
      exit(-1);    
    }    
    else if(father_id == 0)                                                                                 
    {    
      printf("子进程没有退出\n");    
      sleep(1);    
      continue;    
    }    
    else    
    {    
       if(WIFEXITED(status)) //收到信号
       {
        	printf("等待成功,退出码是:%d\n", WEXITSTATUS(status));
       }
       else
       {
     		printf("等待成功,信号是:%d\n", status & 0x7F);
       }
      break;    
    }    
    
    return 0;    
  }    
}  

9. 进程程序替换

9.1 现象

[jyh@VM-12-12-centos study8]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  152 Mar  7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar  7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8416 Mar  7 16:56 procReplace
-rw-rw-r-- 1 jyh jyh  271 Mar  7 16:56 procReplace.c
[jyh@VM-12-12-centos study8]$ cat procReplace.c
#include <stdio.h>
#include <unistd.h>


int main()
{
  printf("begin......\n");
  printf("begin......\n");
  printf("begin......\n");
  
  execl("/bin/ls", "ls", "-a", "-l", NULL);
  
  printf("end.....\n");
  printf("end.....\n");
  printf("end.....\n");
  return 0;
}
[jyh@VM-12-12-centos study8]$ ./procReplace 
begin......
begin......
begin......
total 32
drwxrwxr-x  2 jyh jyh 4096 Mar  7 16:56 .
drwxrwxr-x 15 jyh jyh 4096 Mar  7 15:42 ..
-rw-rw-r--  1 jyh jyh  152 Mar  7 16:55 Makefile
-rw-rw-r--  1 jyh jyh 2045 Mar  7 16:43 proc.c
-rwxrwxr-x  1 jyh jyh 8416 Mar  7 16:56 procReplace
-rw-rw-r--  1 jyh jyh  271 Mar  7 16:56 procReplace.c

我们发现上述代码中执行procReplace.c这个进程的时候,然后调用execl来执行另外一个程序时,procRepalce.c进程被替换了,并没有执行完execl再来执行procReplace.c程序,这就是程序替换。

9.2 原理

进程有对应的PCB,最后数据和代码别加载到内存中,调用execl函数时,该进程的代码和数据完全被新程序替换,执行新程序。进程的程序替换并没有创建新的进程,调用execl函数前后该进程的PID并没有变化。

[jyh@VM-12-12-centos study8]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  152 Mar  7 16:55 Makefile
-rw-rw-r-- 1 jyh jyh 2045 Mar  7 16:43 proc.c
-rwxrwxr-x 1 jyh jyh 8624 Mar  7 17:21 procReplace
-rw-rw-r-- 1 jyh jyh  578 Mar  7 17:21 procReplace.c
[jyh@VM-12-12-centos study8]$ cat procReplace.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    printf("我是子进程,我的PID:%d\n", getpid());
    execl("/bin/ls","ls", "-a", "-l");
  }
  sleep(5);

  printf("我是父进程,我的PID:%d\n", getpid());
  waitpid(id, NULL, 0);

  return 0;
}

[jyh@VM-12-12-centos study8]$ ./procReplace 
我是子进程,我的PID:8085
total 32
drwxrwxr-x  2 jyh jyh 4096 Mar  7 17:21 .
drwxrwxr-x 15 jyh jyh 4096 Mar  7 15:42 ..
-rw-rw-r--  1 jyh jyh  152 Mar  7 16:55 Makefile
-rw-rw-r--  1 jyh jyh 2045 Mar  7 16:43 proc.c
-rwxrwxr-x  1 jyh jyh 8624 Mar  7 17:21 procReplace
-rw-rw-r--  1 jyh jyh  578 Mar  7 17:21 procReplace.c
我是父进程,我的PID:8084
[jyh@VM-12-12-centos study8]$ 

上述观察到:子进程中调用execl函数并不会影响父进程,说明操作系统在调用execl时对代码和数据完成了写时拷贝。这里的写时拷贝是拷贝的代码,说明写时拷贝可以发生在代码区。

9.3 接口

  1. execl
int execl(const char* path, const char* arg, ...);
//path:路径
//arg:参数
//最后参数列表必须以NULL结尾

实例

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

int main()
{
  execl("/bin/pwd", "pwd", NULL);
  return 0;
}
  1. execv
int execv(const char* path, char* const argv[]);
//path:路径
//argv:字符串数组
//argv[]中必须以NULL结尾

实例

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

int main()
{
  char* const argv[] = {"ls", "-a", "-l", NULL};
  execv("/bin/ls", argv);
  return 0;
}
  1. execlp
int execlp(const char* file, const char* arg, ...);
//file:文件
//arg:参数
//参数列表必须以NULL结尾
//作用:系统自动在环境变量PATH中进行查找file按照参数执行

实例

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

int main()
{
  execlp("ls", "ls", "-a", "-l", NULL);
  return 0;
}
//这里的两个ls不一样,第一个ls是执行文件名,第二个ls及以后是怎样执行的参数
  1. execvp
int execvp(const char* file, char* const argv[], ...);

实例

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

int main()
{ 
  char* const argv[] = {"ls", "-a", "-l", NULL};

  execvp("ls", argv);
  return 0;
}
  1. execle
int execle(const char* path, const char* arg, ..., char* const envp[]);
//path:绝对路径
//arg:参数
//envp:自定义环境变量

实例

[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
|   |-- Makefile
|   |-- otherTest
|   `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c

1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>

int main()
{
  char* const myenv[] = {"MYENV=study9/test.c", NULL};

  execle("./exec/otherTest", "otherTest", NULL, myenv);
  return 0;
}

[jyh@VM-12-12-centos study9]$ cat ./exec/otherTest.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;

int main()
{
  for(int i = 0; i < 3; ++i)
  {
    cout << "我是exec路径下的程序,我的PID是: " << getpid() << " " << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
    sleep(1);
  }
  return 0;
}

[jyh@VM-12-12-centos study9]$ cat Makefile 
mytest:test.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f mytest

[jyh@VM-12-12-centos study9]$ cat ./exec/Makefile 
otherTest:otherTest.cc
	g++ -o $@ $^
.PHONY:clean
clean:
	rm -f otherTest

[jyh@VM-12-12-centos study9]$ ./mytest 
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
我是exec路径下的程序,我的PID是: 6359 MYENV:study9/test.c
[jyh@VM-12-12-centos study9]$ 

这里就可以掌握使用了。但是看下面的问题:

[jyh@VM-12-12-centos study9]$ clear
[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
|   |-- Makefile
|   |-- otherTest
|   `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c

1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cd exec/
[jyh@VM-12-12-centos exec]$ cat otherTest.cc 
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;

int main()
{
  for(int i = 0; i < 3; ++i)
  {
    cout << "------------------------------------------------------" << endl;
    cout << "我是exec路径下的程序,我的PID是: " << getpid() << endl;
    cout << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
    cout << "PATH:"<< (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl;
    cout << "------------------------------------------------------" << endl;
    sleep(1);
  }
  return 0;
}

[jyh@VM-12-12-centos exec]$ ./otherTest 
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8867
MYENV:NULL
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
[jyh@VM-12-12-centos exec]$ cd ..
[jyh@VM-12-12-centos study9]$ pwd
/home/jyh/linux_-stu/study9
[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>

int main()
{
  char* const myenv[] = {"MYENV=study9/test.c", NULL};

  execle("./exec/otherTest", "otherTest", NULL, myenv);

  return 0;
}

[jyh@VM-12-12-centos study9]$ ./mytest 
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 8910
MYENV:study9/test.c
PATH:NULL
------------------------------------------------------
[jyh@VM-12-12-centos study9]$ 

现象:当本程序中设定自定义环境变量,另外的程序中有系统环境变量,此时运行本程序时,另外的程序中的系统环境变量为NULL。说明了:自定义环境变量是覆盖式传入,覆盖了系统的环境变量,所以导致这里系统环境变量为NULL。这里怎么解决呢?

int putenv(char* string);
//作用:改变或者添加一个环境变量
[jyh@VM-12-12-centos study9]$ tree
.
|-- exec
|   |-- Makefile
|   |-- otherTest
|   `-- otherTest.cc
|-- Makefile
|-- mytest
`-- test.c

1 directory, 6 files
[jyh@VM-12-12-centos study9]$ cat ./exec/otherTest.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;

int main()
{
  for(int i = 0; i < 3; ++i)
  {
    cout << "------------------------------------------------------" << endl;
    cout << "我是exec路径下的程序,我的PID是: " << getpid() << endl;
    cout << "MYENV:"<< (getenv("MYENV") == NULL ? "NULL" : getenv("MYENV")) << endl;
    cout << "PATH:"<< (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl;
    cout << "------------------------------------------------------" << endl;
    sleep(1);
  }
  return 0;
}

[jyh@VM-12-12-centos study9]$ cat test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
  extern char** environ;

  char* const myenv[] = {"MYENV=study9/test.c", NULL};
  putenv("MYENV=study9/test.c"); 
  execle("./exec/otherTest", "otherTest", NULL, environ);

  return 0;
}

[jyh@VM-12-12-centos study9]$ ./mytest 
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
------------------------------------------------------
我是exec路径下的程序,我的PID是: 14363
MYENV:study9/test.c
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/jyh/.local/bin:/home/jyh/bin
------------------------------------------------------
[jyh@VM-12-12-centos study9]$ 

这里考虑一个问题:环境变量具有全局属性,可以被子进程继承下去,这里怎么做到的呢?

因为所有的指令都是bash的子进程,而bash执行指令都可以使用execl来执行,我们要把bash的环境变量交给子进程,子进程只是需要调用execle,再把对应的bash的环境变量作为参数即可被子进程继承下去。

  1. execvpe
int execvpe(const char* file, char* const argv[], char* const envp[]);
  1. execve
int execve(const char* filename, char* const argv[], char* const envp[]);

10. 编写极简shell

[jyh@VM-12-12-centos demo]$ ll
total 8
-rw-rw-r-- 1 jyh jyh   74 Mar  8 17:10 Makefile
-rw-rw-r-- 1 jyh jyh 1716 Mar  8 17:31 mybash.c
[jyh@VM-12-12-centos demo]$ cat mybash.c 
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/types.h>

//模拟实现极简bash命令行

#define COMMANDMAXSIZE 1024
#define ARGVMAXSIZE 64


int split(char* command_string, char* argv[])
{
  assert(command_string != NULL && argv != NULL);
  argv[0] = strtok(command_string, " ");
  if(argv[0] == NULL) 
  {
    return -1;
  }
  int i = 1;
  while((argv[i++] = strtok(NULL, " ")));
  //while(1)
  //{
  //  argv[i] = strtok(NULL, " ");
  //  if(argv[i] == NULL)
  //  {
  //    break;
  //  }
  //  ++i;
  //}
  return 0;
}

void debugOut(char* argv[])
{
  int i = 0;
  while(argv[i] != NULL)
  {
    printf("%d->%s\n", i, argv[i]);
    ++i;
  }
  //for(int i = 0; argv[i] != NULL; ++i)
  //{
  //  printf("%d->%s\n", i, argv[i]);
  //}
}


int main()
{
  while(1)
  {
    char command_string[COMMANDMAXSIZE] = {0};
    printf("[name@my_root currrent_path]# ");
    fflush(stdout);
    char* s = fgets(command_string, sizeof(command_string), stdin);
    assert(s != NULL);
    (void)s; //保证release版本时,因为去掉assert()导致ret未被使用,而带来的编译告警
    command_string[strlen(command_string) - 1] = '\0'; //去掉回车导致的换行
    char* argv[ARGVMAXSIZE] = {NULL};
    int ret = split(command_string, argv); //命令字符串切割
    if(ret != 0)
    {
      continue;
    }
    //debugOut(argv);

    pid_t id = fork();
    assert(id >= 0);
    if(id == 0) //子进程
    {
      execvp(argv[0], argv);
      exit(0); 
    }
    
    //父进程
    int status = 0;
    waitpid(id, &status, 0);

    //子进程去执行对应的命令,父进程等待子进程
  }
  return 0;
}

[jyh@VM-12-12-centos demo]$ cat Makefile 
mybash:mybash.c
	gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
	rm -f mybash

[jyh@VM-12-12-centos demo]$ 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脚踏车(crush)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值