Linux控制流③:fork和execve

Unix中提供许多从C程序中操作进程的系统调用,其中最重要的两个是fork和execve。它们也是Linux下实现并发编程的关键。

 

【fork】

pid_t fork(void);

该系统调用创建一个几乎和父进程完全一样的子进程。子进程得到与父进程虚拟地址空间的一个副本,即包含的代码段、数据段、运行时堆、用户栈。另外,子进程还获得父进程打开的文件描述符。对应其在内核中的进程描述符task_struct 不完全相同。 两者最大的区别在于:PID即进程标识符不同

fork函数的主要特点包括:

①fork函数其与其它系统调用不同之处在于其:一次调用,两次返回。

在父进程中被调用一次。父进程中fork返回子进程的ID,而在子进程中,返回0 ,基于返回值即可确定是在父进程还是子进程中。

 

②fork创建完子进程后,两者并发执行。内核依据调度程序交替的执行父子进程中的逻辑流指令。

③相同但独立的地址空间,fork调用创建子进程之初,子进程和父进程的地址空间是相同的。也就意味着两个进程可以访问相同的地址空间内容。相同的代码段,相同的全局变量等。但一旦其中一个进程需要修改地址空间的内容,将触发写时复制

copy on write:当一个进程修改地址空间内容时,将原先内容拷贝一份,以确保修改是自己私有的地址空间。

通常而言,fork出来的子进程通常完成一些较简单的任务,因此很可能不会修改原先地址空间,那么就不会触发写时复制,因此linux中此类机制可以达到很快的速度。

④共享打开的文件描述符。子进程共享父进程打开的文件描述符,其通过引用到父进程描述符task_struct中的打开文件描述符,从而父子进程可以共享文件。

fork实例:

父进程创建栈中变量X=1  调用fork函数

在返回到子进程中,++x 那么输出2

在返回到父进程中,--x   那么输出0

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

int main()
{
    pid_t pid;
    int x=1;
    pid=fork();
    if(pid==0)
    {
        printf("child x=%d \n",++x);
        exit(0);
    }
    printf("parent x=%d\n",--x);
    exit(0);
}

执行结果: 

 

【回收子进程】

当一个进程因为一些原因终止时(例如收到sys kill 信号) 内核不是立即将它从系统中清除。而是被保存为已终止状态,直到被它的父进程回收。父进程在回收子进程时,收集子进程的退出状态信息,然后就可以被清除。

僵死进程:一个终止了,但还没有被回收的进程

特别地,如果一个父进程提前终止了,那么内核设定init (PID=1)进程成为它的孤儿进程的父进程。由于Init进程在系统运行期间始终存活,因此总能设定成功,即总能通过init进程回收那些孤儿僵死进程。

而父进程可以显示地等待子进程终止。

pid_t waitpid(pid_t pid,int * statusp,int options)

默认情况下,options=0  那么调用waitpid的进程会被挂起,直到等待的进程有一个子进程终止,同时waitpid返回终止的进程PID

 

【execve】

execve系统调用在当前进程上下文中加载并运行一个新的程序

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

函数加载并运行filename指定的程序,并且可以指定参数列表 argv  以及环境变量 envp

当发生错误,找不到文件时,返回到调用程序。fork和execve都是创建新的逻辑执行流,它们的不同之处在于fork创建一个全新的进程,而execve只是在当前进程中加载新的程序。

 

 地址空间新进程PID返回方式
fork子进程拥有独立的地址空间子进程拥有不同PID返回到父子进程中
execve在原先地址空间上执行仍属于原先进程 PID不变新程序执行完成后返回

 

 

【实验与演示】

Linux shell是一个典型的交互式应用程序,代表用户执行其它程序。

其基本的实现方式是:

  1. 读取一个用户的一个命令行
  2. 解析命令行,分离出调用指令,调用参数
  3. 代表用户执行相应的程序

 

首先是主函数 main 它不断等待用户输入,并调用解析函数以及执行函数


int main()
{
    char cmdline[MAXLINE];
    while(1)
    {
        printf(">");
        fgets(cmdline,MAXLINE,stdin);  //获取一行
        if(feof(stdin))
            exit(0);
        eval(cmdline);
    }

}

解析命令行:主要是依据空格分割出参数

nt parseline(char* buf,char** argv)
{
    char* delim;
    int argc;
    int bg;
    buf[strlen(buf)-1]=' ';
    while(*(buf) && (*buf==' '))
        buf++;
    argc=0;
    while((delim=strchr(buf,' ')))
    {
        argv[argc++]=buf;
        *delim='\0';
        buf=delim+1;
        while(*buf && (*buf==' '))
            buf++;
    }
    argv[argc]=NULL;
    if(argc==0)
        return 1;
    if((bg=(*argv[argc-1]=='&'))!=0)
        argv[--argc]=NULL;
    return bg;
}

执行函数:首先在主进程中,fork出子进程=》在子进程中调用execve加载并执行相应程序

void eval(char* cmdline)
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;
    pid_t pid;
    strcpy(buf,cmdline);
    bg=parseline(buf,argv);
    if(argv[0]==NULL)
        return;
    if(!builtin_command(argv))
    {

        if((pid=fork())==0)  //fork返回到子进程
        {
            if(execve(argv[0],argv,environ)<0)  //加载并执行 argv[0]指定的程序
            {
                printf("%s : command not found !\n",argv[0]);
                exit(0);
            }
        }
        if(!bg)
        {
            int status;
            if(waitpid(pid,&status,0)<0)
            {
                printf("wait error\n");
            }
        }
        else
            printf("%d %s",pid,cmdline);
    }
    return;
}
//内置的命令
int builtin_command(char **argv)
{
    if(!strcmp(argv[0],"quit"))
        exit(0);
    if(!strcmp(argv[0],"&"))
        return 1;
    if(!strcmp(argv[0],"clear"))
    {
        system("clear");
        return 2;
    }

    return 0;
}

 

运行结果:

通过 /bin/ls 指定execve的调用程序为 /bin/ls 同时指定 -al 参数  打印出当前目录下所有文件信息

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值