Linux——进程控制

1. 进程创建

1.1. 写实拷贝

1.2. fork常规用法

一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数 。

1.3. fork调用失败的原因

系统中有太多的进程
实际用户的进程数超过了限制

2. 进程终止

2.1. 进程退出场景解读

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

main函数的返回值,通常表明进程退出的情况

  • 返回值为0:代码运行完毕,结果正确
  • 返回值非0:代码运行完毕,结果不正确
    • 返回值是退出码,退出码不同表示不同的出错原因
    • echo $?:查看退出码(最近运行的一个进程)
    • 进程退出码会写到task_struct中
  • 代码异常:退出码意义,进程收到了信号
  • strerror查看退出码
  • 可以自己定义退出码:exit(20)

2.2. 常见进程退出方法

  1. main函数返回值
  2. exit/_exit退出
    1. exit是函数调用,_exit是系统调用,exit底层还是调用的_exit系统调用,他们是上下层关系
    2. exit退出会进行缓冲区刷新
    3. _exit退出不会进行缓冲区刷新
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
   int i = 0;
   for(; i < 180; i++)
   {
        printf("%d,%s\n",i,strerror(i));
   }
    int num = 10;
    printf("进程退出:%d",num);
    _exit(1);
    printf("进程退出:%d",num);
    return 0;
}

3. 进程等待

解决僵尸问题:进程等待

3.1. 为什么要进行进程等待

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法
杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,
或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.2. 进程等待是什么

waitpid函数解释

status解释:

  • 表示子进程退出的状态,类似与main函数的返回值
  • 是输出型参数:会在函数内部改变,返回给外部,有点像取地址调用
  • status:存储子进程退出状态(需用宏解析,如 WIFEXITED, WEXITSTATUS 等)
printf("%d\n",(status>>8)&0xff);
printf("%d\n",WEXITSTATUS(status));
 //这两种方法都可以获取进程退出码

返回值解释

  • >0:等待指定的子进程。
  • -1:等待任意子进程(等价于 wait)。
  • 0:等待同进程组的子进程。
  • <-1:等待进程组 ID 为 |pid| 的子进程。

option解释:

  • 控制行为
  • WNOHANG:如果子进程没有退出就立即返回--->非阻塞调用
    • 例子:计算机卡住了---->阻塞调用
  • 如果子进程没有退出,父进程会阻塞在wait调用处,类似于scanf函数
  • 阻塞调用:父进程一直等待子进程结束调用,不做其它事情。
  • 非阻塞调用(none block):在等待子进程的期间做其他事情。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


int main()
{
    printf("我的程序要运行了!\n");
    //execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
    execl("/usr/bin/top", "top", NULL);
    printf("我的程序运行完毕了\n");
    return 0;
}


//// 函数指针类型
//typedef void (*func_t)();
//
//#define NUM 5
//func_t handlers[NUM+1];
//
//// 如下是任务
//void DownLoad()
//{
//    printf("我是一个下载的任务...\n");
//}
//void Flush()
//{
//    printf("我是一个刷新的任务...\n");
//}
//void Log()
//{
//    printf("我是一个记录日志的任务...\n");
//}
//
//
//
//// 注册
//
//void registerHandler(func_t h[], func_t f)
//{
//    int i = 0;
//    for(; i < NUM; i++)
//    {
//        if(h[i] == NULL)break;
//    }
//    if(i == NUM) return;
//    h[i] = f;
//    h[i+1] = NULL;
//}
//
//int main()
//{
//    registerHandler(handlers, DownLoad);
//    registerHandler(handlers, Flush);
//    registerHandler(handlers, Log);
//
//
//    pid_t id = fork();
//    if(id == 0)
//    {
//        //子进程
//        int cnt = 3;
//        while(1)
//        {
//            sleep(3);
//            printf("我是一个子进程, pid : %d, ppid : %d\n", getpid(), getppid());
//            sleep(1);
//            cnt--;
//            //int *p = 0;
//            //*p= 100;
//           // int a = 10;
//           // a /= 0;
//        }
//        exit(10);
//    }
//
//    // 父进程
//    while(1)
//    {
//        int status = 0;
//        pid_t rid = waitpid(id, &status, WNOHANG);
//        if(rid > 0)
//        {
//            printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, (status>>8)&0xFF, status&0x7F) ; // rid
//            break;
//        }
//        else if(rid == 0)
//        {
//            // 函数指针进行回调处理
//            int i = 0;
//            for(; handlers[i]; i++)
//            {
//                handlers[i]();
//            }
//            printf("本轮调用结束,子进程没有退出\n");
//            sleep(1);
//        }
//        else
//        {
//            printf("等待失败\n");
//            break;
//        }
//
//    }
//
//
//    //父进程
//    //1. 子进程退出
//    //2. 子进程没退出呢??
//    //pid_t rid = wait(NULL);
//    //int status = 0;
//    //pid_t rid = waitpid(id, &status, 0);
//    //if(rid > 0)
//    //{
//    //    if(WIFEXITED(status))
//    //        printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, WEXITSTATUS(status), status&0x7F) ; // rid
//    //    else
//    //        printf("子进程退出异常!\n");
//    //    //printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, (status>>8)&0xFF, status&0x7F) ; // rid
//    //}
//    //else
//    //{
//    //    printf("wait failed: %d: %s\n", errno, strerror(errno));
//    //}
//    return 0;
//}
//
//
//void fun()
//{
//    printf("fun begin!\n");
//    _exit(4);
//    printf("fun end!\n");
//}
//
////int main()
////{
////
////    //fun();
////    printf("main!");
////
////    sleep(2);
////
////    _exit(23);
////
////    //int i = 0;
////    //for(; i < 200; i++)
////    //{
////    //    printf("%d->%s\n", i, strerror(i));
////    //}
////
////
////    ////printf("hello world\n");
////
////    //FILE *fp = fopen("log.txt", "r");
////    //if(fp == NULL) return 13;
////
////    //// 读取
////    //fclose(fp);
////    return 0;
////}

4. 进程程序替换

4.1. 替换原理:

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

  1. 只要是能在linux上跑的语言都能通过程序替换来跑
  2. 程序替换不创建新的进程

4.2. 替换函数使用

上面的接口不是系统调用最终都要调用下面这个系统调用接口

int execve(const char *path, char *const argv[], char *const envp[]);

  • 函数解释:
    • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
      如果调用出错则返回-1,execute系列函数不用对返回值做判
      所以exec函数只有出错的返回值而没有成功的返回值

小知识点

linux运行python:

linux运行shell脚本:

4.3. 命名理解

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量 ,覆盖式的写入当前进程(可以是当前进程的子进程)环境变量,

#include<unistd.h>

int main()
{
    printf("我的程序要运行了!\n");
    //execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
    execl("/usr/bin/top", "top", NULL);
    printf("我的程序运行完毕了\n");
    return 0;
}

运行该程序的时候会执行top命令。

  • putenv():在当前程序中获得

4.4. 补充知识点

导入环境变量到当前进程

  • putenv()到environ中
  • execvpe(path,argv,environ)
  • //这里传递将新增的环境变量导入到environ中,直接传参的时候传environ
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

extern char ** environ;

char* const addenv[]=
{
    (char* const)"myenv1=1234",
    (char* const)"myenv2=1234",
    (char* const)"myenv3=1234",
    (char* const)"myenv4=1234",
    (char* const)"myenv5=1234",
    NULL
};

int main()
{
    printf("程序替换前\n");
    //替换命令
    //execl("/usr/bin/ls","/usr/bin/ls", "-la", "-ln", NULL);
    //不携带路径
    //execlp("ls","ls","-a","-l", NULL);
    //命令行参数以vector的形式
    //execv("/usr/bin/ls",argv);
    char* const argv[]=
    {
       (char* const)"-a",
       (char* const)"-l",
       (char* const)"-ln",
       NULL 
    };
    int i = 0;
    for(; addenv[i]; i++)
    {
        putenv(addenv[i]);
    }
    //不需携带路径和命令行参数
    execvpe("./oother",argv,environ);
    char** e = environ;
    for(;*e; *e++)
    {
        printf("%s\n", *e);
    }
    //execl("/usr/bin/ls","ls", "-la", "-ln", NULL);
    
    //替换自己写的程序 
    //execl("/usr/bin/python3","python","other.py", NULL);
    //execl("/usr/bin/bash","bash","other.sh", NULL);
    //execl("./other","other", NULL);
    //printf("程序替后\n");
  
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<sys/types.h>
#include<unistd.h>



int main()
{
    std::cout << "hello C++" << std::endl;
    std::cout << "mypid is:" <<getpid()<<"myppid is:"<<getppid()<< std::endl;

    char** e = environ;
    for(;*e; *e++)
    {
        printf("%s\n", *e);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值