菜鸟学习历程【17】进程控制编程

来源:互联网 发布:数据库安全性设计 编辑:程序博客网 时间:2024/06/08 17:25

进程控制编程

进程:进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;

进程 程序 程序执行的实例 放到磁盘的可执行文件 进程不可在计算机之间迁移 程序通常对应着文件、静态和可以复制 动态 静态 暂时:进程是一个状态变化的过程 长久:程序可长久保存

进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)

进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

进程的生命周期

创建: 每个进程都是由其父进程创建,进程可以创建子进程,子进程又可以创建子进程的子进程

运行: 多个进程可以同时存在,进程间可以通信

撤销: 进程可以被撤销,从而结束一个进程的运行

进程的状态(运行):

执行状态:进程正在占用CPU

就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片

等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒

Linux进程

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。

也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。

进程ID:(PID)标准进程的唯一数字
父进程:(PPID)
启动进程的用户ID(UID)

进程互斥:进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止。
临街资源:(共享资源)一次只允许一个进程访问的资源称为临界资源
临界区:进程中访问临界资源的那段程序代码称为临界区
进程同步: 一组并发进程按一定的顺序执行的过程称为进程间的同步,具有同步关系一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
进程调度:按一定算法,从一组待运行的进程中选出一个来占有CPU运行。
调度方式:抢占(优先级) 、非抢占式

算法:

  • 先来先服务调度算法
  • 短进程优先调度算法
  • 高优先级优先调度算法
  • 时间片轮转法

目前,后两者最为常用。

死锁:多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程都将永远不能再向前推进


获取ID

pid_t getpid(void) 获取进程IDpid_t getppid(void) 获取父进程ID例如:#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(){    printf("PID = %d\n", getpid());    printf("PPID = %d\n", getppid());    while(1);    return 0;}

进程创建

1.fork()

pid_t fork(void)  创建子进程

子进程的ID号为父进程的ID号 + 1

特别:返回值返回两次,父进程返回子进程的ID号,子进程返回0

#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>int main(){    pid_t pid;    pid = fork();    if(-1 == pid)    {        perror("fork");        exit(1);    }    else if(0 == pid) //子进程返回0,子进程执行部分    {        printf("Child Process Id = %d\nParent Process Id = %d\n", getpid(), getppid());    }    else  //父进程执行部分    {        printf("Parent Process Id = %d\n", getpid());    }    return 0;}

思考:下面这段代码的最终输出结果是???

#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(){    pid_t pid;    pid = fork();    int count = 0;    count++;    printf("%d\n", count);    return 0;}

注意:使用fork()创建进程后,会有两个进程存在,对于fork而言,这两个进程在不同的地址空间(之前我们讲过对于一个进程而言会有一个4G的虚拟内存),那么对于fork后的两个进程,会存在两个地址一模一样的空间,子进程会将父进程的所有代码(除去各自独有的代码)复制到自己的代码段。
写时复制:当某个进程访问某个变量,并要修改时,则会分配另一个空间。

对于上面这个情况,当子进程尝试count++时,会改变count的值,此时系统会开辟另一个空间给子进程,此时count为0,那么经过自加后,输出count为1;对于父进程而言,也是一样的。

所以,上面的代码最终结果是:

11

2.vfork()

 pid_t vfork(void);
  • vfork的子进程必须加exit()退出,否则会出错;
  • 子进程先运行,子进程运行后,再执行父进程
  • vfork的子进程与父进程共享相同的资源

下面这段代码的输出结果又是如何的呢?

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>int main(){    pid_t pid;    int count = 0;    pid = vfork();    if(-1 == pid)    {        perror("vfork");        exit(1);    }    else if(0 == pid)    {        count++;        printf("count = %d\n", count);        exit(1);    }    else    {        count++;        printf("count = %d\n", count);    }    return 0;}

我们知道,vfork的子进程和父进程共享相同的资源,那么在执行子进程中的count++时,count由原来的0变成1,输出1;
执行父进程的count++时,count又由1变成2,输出2;
所以最终结果是:

12

如果我将程序改动如下,结果又是什么呢?

......int main(){    pid_t pid;    pid = vfork();    int count = 0;    ......  }

将pid = vfork();与int count = 0;的位置交换,最终结果会是如何?
结果如下:

11

为什么呢?
此时子进程和父进程依旧共享相同的资源,但它也共享了”int count = 0”这句话,所以在子进程执行完毕后,count = 1;当父进程开始执行时,先执行的是int count = 0,将原来的1又置为0,再经过自加操作,那么输出结果还是1;

3.exec函数族

exec启动一个新程序,替换原有的进程,因此进程的PID不会改变

1.int execl(const char *path, const char *arg, …);

path:被执行程序名(含完整路径)。arg: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。

例如:
使用execl在进程中调用ls,显示当前目录下的文件信息。

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>int main(){    pid_t pid;    pid = vfork();    if(-1 == pid)    {        perror("vfork");        exit(1);    }    else if(0 == pid)    {        printf("Child process:%d\n", getpid());        execl("/bin/ls",NULL);    }    else    {        printf("Parent process\n");    }    return 0;}

2.int execv(const char *path, char *const argv[]);
参数:
path:被执行程序名(含完整路径)。
argv[]: 被执行程序所需的命令行参数数组。

例如:

#include <stdio.h>#include <sys/types.h>#include <unistd.h>int main(){    char *argv[] = {"ls",NULL};    execv("/bin/ls", argv);    return 0;}

进程等待

先介绍两个概念,一个是孤儿进程,一个是僵尸进程。

孤儿进程:其父进程在它之前结束,不能再对它进行回收

僵尸进程:子进程结束后,没有被回收的时间段内,被称为僵尸进程。

1.pid_t wait(int *status);

功能:阻塞该进程,直到其某个子进程退出。

例如:

#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>#include <sys/wait.h>int main(){    pid_t pid;    pid = fork();    int status;    if(-1 == pid)    {        perror("fork");        exit(1);    }    else if(0 == pid)    {        sleep(2);        printf("Child Process\n");        exit(6);    }    else    {           printf("Parent Process\n");        wait(&status);  //如果不需要知道它的退出类型,wait的形参写NULL也可以        if(WIFEXITED(status))        {            printf("Exit Normally %d\n", WEXITSTATUS(status));        }    }    return 0;}

如果不写wait(),在子进程sleep的过程中,其父进程就已经结束了,那么子进程就会变成孤儿进程,但加上wait后,父进程直到子进程结束才会退出。

...else if(0 == pid)    {           printf("Child Process\n");        exit(6);    }    else    {           sleep(2);        printf("Parent Process\n");        wait(&status);          if(WIFEXITED(status))        {            printf("Exit Normally %d\n", WEXITSTATUS(status));        }    }    ...

如果我们将子进程中的sleep函数放在父进程中,那么在子进程执行结束后,父进程需要沉睡两秒后才执行,这两秒内,父进程没有去回收子进程,所以称子进程为僵尸进程,但两秒钟后,子进程又被回收。

2.pid_t waitpid(pid_t pid, int *status, int options);

参数pid:(欲等待的子进程识别码)

pid < -1: 等待进程组识别码为pid绝对值的任何子进程。
pid = -1: 等待任何子进程,相当于wait()。
pid = 0: 等待进程组识别码与目前进程相同的任何子进程。
pid > 0: 等待任何子进程识别码为pid的子进程。

参数option:(通常设置为0)

WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。

WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

进程退出

exit()与_exit()

exit():在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。

_exit():直接使进程停止,清除其使用的内存,并清除缓冲区中内容

所以,我们通常选择使用exit(),而不是用_exit();

原创粉丝点击