Linux C笔记 之 进程

Linux编程

  • Linux _c 之 进程
      • 1、进程是什么?
      • 2、进程的定义:
      • 3、进程与程序
      • 4、fork()函数
      • 5、vfork()函数
      • 6、fork() 与 vfock() 都是创建一个进程,他们的区别是
      • 7、EXEC函数族
      • 7、特殊进程

Linux _c 之 进程

1、进程是什么?

  • 一个运行着一个或多个线程(thread)的地址空间好这些线程所需的资源。

2、进程的定义:

  • 是OS的概念(当执行一个程序时,对OS来说就相当于创建了一个进程)
  • 创建——OS级上,分配系统资源(时间空间资源),进程控制块(堆栈段)、数据段、代码段。其描述的是 程序的动态执行过程

3、进程与程序

  • 程序—静态,是保存在磁盘上的指令的有序集合,没有任何执行的概念
  • 进程—动态,是程序执行的一个过程,包括OS对其创建、调度、销毁
    创建的步骤,OS完成以下工作:
    A、给新创建的进程分配一个内部标识PID,在内核中建立进程结构
    B、复制父进程的环境
    C、为新进程分配资源,包括进程的映像所需的所有元素(数据段、代码段、堆栈段)
    D、复制父进程地址空间的内容到新进程地址空间
    E、置新进程的状态为就绪态,插入OS的就绪队列

4、fork()函数

由fork创建的进程称为子进程(child process),此函数调用一次,返回两次。
子进程返回0值,父进程返回子进程的I 子进程是父进程的副本,它将获得父进程的数据空间,堆栈等资源

注意:有副本,但不共享
推荐下面这篇文章:
fork()详解 文章,请点击.

5、vfork()函数

子进程不会复制父进程的环境,但子进程直接运行在父进程的地址空间,不能进行写操作。
子进程运行时父进程会阻塞,直到子进程执行了exec族或exit,父进程才会执行

6、fork() 与 vfock() 都是创建一个进程,他们的区别是

	1)fork(): 父子进程的执行次序不确定。
		vfork():保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程)之后父进程才可能被调度运行。
	2)fork(): 子进程拷贝父进程的地址空间   ,子进程是父进程的一个复制品。
		vfork():子进程共享父进程的地址空间(准确来说,在调用 exec(进程替换) 或 exit(退出进程) 之前与     父进程数据是共享的)

7、EXEC函数族

extern char** environ

  • exec函数族说明
    exec 函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是 Linux 下任何可执行的脚本文件。

  • 在Linux中使用exec函数族主要有两种情况:

  1. 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何 exec 函数族让自己重生;
  2. 如果一个进程想执行另一个程序,那么它就可以调用 fork 函数新建一个进程,然后调用任何一个 exec,这样看起来就好像通过执行应用程序而产生了一个新进程

exec函数族语法
所需头文件:#include

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

函数助记表

位置 含义 函数
前4位 统一为:exec
第5位 l:参数传递为逐个列举方式 execl、execle、execlp
v:参数传递为构造指针数组方式 execv、execve、execvp
第6位 e:可传递新进程环境变量 execle、execve
p:可执行文件查找方式为文件名 execlp、execvp
事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。在使用exec函数族时,一定要加上错误判断语句。exec 很容易执行失败,其中最常见的原因有:
   ①  找不到文件或路径,此时 errno 被设置为 ENOENT。
   ②  数组argv 和envp  忘记用NULL结束,此时,errno被设置为 EFAUL。
   ③  没有对应可执行文件的运行权限,此时 errno 被设置为EACCES。
 使用execle和execve可以自己向执行进程传递环境变量,但不会继承Shell进程的环境变量,而其他四个exec函数则继承Shell进程的所有环境变量。
  • exec函数族实例
int main()
{
    char *envp[]={"PATH=/tmp","USER=noodles","STATUS=testing",NULL};
    char *argv_execv[]={"echo", "excuted by execv", NULL};
    char *argv_execvp[]={"echo", "executed by execvp", NULL};
    char *argv_execve[]={"env", NULL};
    if(fork()==0){
        if(execl("/bin/echo", "echo", "executed by execl", NULL)<0)
        perror("Err on execl");
    }
    if(fork()==0){
        if(execlp("echo", "echo", "executed by execlp", NULL)<0)
        perror("Err on execlp");
    }
    if(fork()==0){
        if(execle("/usr/bin/env", "env", NULL, envp)<0)
        perror("Err on execle");
    }
    if(fork()==0){
        if(execv("/bin/echo", argv_execv)<0)
        perror("Err on execv");
    }
    if(fork()==0){
        if(execvp("echo", argv_execvp)<0)
        perror("Err on execvp");
    }
    if(fork()==0){
        if(execve("/usr/bin/env", argv_execve, envp)<0)
        perror("Err on execve");
    }
    sleep(1);
    return 0;
}

执行结果为:

$ ./fork_C 
executed by execl
executed by execlp
PATH=/tmp
USER=noodles
STATUS=testing
excuted by execv
executed by execvp
PATH=/tmp
程序里调用了2个Linux常用的系统命令,echo和env。echo会把后面跟的命令行参数原封不动的打印出来,env用来列出所有环境变量。

由于各个子进程执行的顺序无法控制,所以有可能出现一个比较混乱的输出–各子进程打印的结果交杂在一起,而不是严格按照程序中列出的次序。
用到了exec函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易出错,被执行文件的位置,权限等很多因素都能导致该调用的失败。最常见的错误是:

1. 找不到文件或路径,此时errno被设置为ENOENT;
2. 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
3. 没有对要执行文件的运行权限,此时errno被设置为EACCES。

7、特殊进程

  • A、孤儿进程
    一个父进程退出,而它的一个或多个子进程还在运行,则这些子进程将成为孤儿进程。孤儿进程将被系统初始化进程(init进程【PID=1】)所收养(内核发现并派给),并由init进程对其完成状态收集工作。(无限循环地wait()子进程的退出)
    父进程退出,子进程还在执行
  • B、僵尸进程
    子进程退出【exit(1)】,父进程还是执行。这种退出并没有完全销毁进程的数据空间,也不能再次获得。但可以通过向其父进程发出SIGKILL信号将其父进程销毁,进而僵尸进程变为孤儿进程有init回收。【CTRL+C或kill】
  • C、守护进程(精灵进程
    它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。
    创建守护进程步骤:
    1)、创建孤儿进程
    2)、创建进程会话
    3)、 重定向标准输入、输出、错误(/dev/null、/dev/zero)
    4)、改变当前进程的工作目录

你可能感兴趣的:(Linux,C,fork,exec族)