【嵌入式学习历程13】Linux进程控制编程

什么是进程
*标准定义:进程是可并发执行的程序,是在一个数据集合上的运行过程。
*通俗地讲:大家都知道,硬盘上的一个可执行文件经常被称为程序,在Linux系统中,当一个程序开始执行后,在开始执行到执行完毕退出这段时间里,它在内存中的部分就被成作一个进程。
*进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;
程序与进程
*程序是放到磁盘的可执行文件
进程是指程序执行的实例
*进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行。通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制
*进程是暂时的,程序使长久的:进程是一个状态变化的过程,程序可长久保存
*进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
*进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。
进程的生命周期
*创建: 每个进程都是由其父进程创建,进程可以创建子进程,子进程又可以创建子进程的子进程
*运行: 多个进程可以同时存在,进程间可以通信
*撤销: 进程可以被撤销,从而结束一个进程的运行
进程的状态
*执行状态:进程正在占用CPU
*就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片
*等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒
Linux进程
*Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。
**Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。
*“数据段”存放的是全局变量、常数以及动态数据分配的数据空间;
*“代码段”存放的是程序代码的数据。
*“堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。

进程分类
*交互进程
*批处理进程
*守护进程

进程的属性
*进程ID(PID):标识进程的唯一数字
*父进程的ID(PPID)
*启动进程的用户ID(UID)和所归属的组(GIU)
*进程状态:运行R、休眠S、僵尸Z
*进程执行的优先级
*进程所连续的终端名
进程资源占用,如占用资源大小(内存、CPU占用量)

获取进程ID


#include 
#include 
#include 

int main()
{
    printf("PID = %d\n", getpid()); //获取子进程ID
    printf("PPID = %d\n", getppid()); //获取父进程ID

    while(1);

    return 0;
}

进程创建fork()
pid_t fork(void)
功能:创建子进程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
0: 子进程
子进程ID(大于0):父进程
-1: 出错

#include 
#include 
int main()
    {
        pid_t pid;
        /*此时仅有一个进程*/
        pid=fork();

              /*此时已经有两个进程在同时运行*/
        if(pid<0)
            printf("error in fork!");
        else if(pid==0)  //子进程执行部分
            printf("I am the child process, ID is %d\n",getpid());
        else   //父进程执行部分
            printf("I am the parent process,ID is %d\n",getpid());
}

在pid=fork()之前,只有一个进程在执行,但在这条语句执行之后,就变成两个进程在执行了,这两个进程的共享代码段,将要执行的下一条语句都是if(pid==0).
两个进程中,原来就存在的那个进程被称作“父进程”,新出现的那个进程被称作“子进程”,父子进程的区别在于进程标识符(PID)不同.

思考运行结果

#include 
#include 
int main(void)
{
       pid_t pid;
        int count=0;
        pid = fork();
        count++;
        printf( “count = %d\n", count );
        return 0;
}

输出:
count = 1
count = 1

count++被父进程、子进程一共执行了两次,为什么count的第二次输出为什么不为2?

子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而不是共享。
在子进程中对count进行加1的操作,并没有影响到父进程中的count值,父进程中的count值仍然为0

进程创建vfork()
vfork()会产生一个新的子进程,其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。
子进程不会继承父进程的文件锁定和未处理的信号。
注意,Linux不保证子进程会比父进程先执行或晚执行,因此编写程序时要留意死锁或竞争条件的发生。
1.vfork的子进程必须加exit()退出,否则会出错;
2.子进程先运行,子进程运行后,再执行父进程
3.vfork的子进程与父进程共享相同的资源

思考运行结果

#include 
#include 
#include 
#include 

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;
}

运行结果:
1
2

fork 与 vfork区别
1、 fork:子进程拷贝父进程的数据段
vfork:子进程与父进程共享数据段

2、 fork:父、子进程的执行次序不确定
vfork:子进程先运行,父进程后运行

exec函数族

  • fork创建一个新的进程,产生一个新的PID。
  • exec启动一个新程序,替换原有的进程,因此进程的PID不会改变
    1、int execl(const char *path, const char *arg, …);
    path:被执行程序名(含完整路径)。
    arg: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
#include 
#include 
#include 
#include 

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;
}

这段代码就是execl函数一个很好的应用,使用execl在进程中调用ls,相当于在Linux终端输入命令ls。

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

#include 
#include 
#include 

int main()
{
    char *argv[] = {"ls",NULL};
    execv("/bin/ls", argv);

    return 0;
}

进程等待
1、僵尸进程
僵尸进程就是已经结束了的进程,但是还没有从进程表中删除。僵尸进程太多会导致进程表里条目满,进而导致系统崩溃,倒是不占用系统资源。
来个通俗的说法:
僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸。
如何产生?
如果一个进程在其终止的时候,自己就回收所有分配给它的资源,系统就不会产生所谓的僵尸进程了

2、僵尸进程产生的过程:
1. 父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。
2. 子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。
3. 因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。

3.如何避免僵尸进程
父进程通过wait和waitpid等函数等待子进程结束,导致父进程挂起。
pid_t wait (int * status)
功能:阻塞该进程,直到其某个子进程退出。

#include 
#include 
#include 
#include 
#include 

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;
}

pid_t waitpid (pid_t pid, int * status, int options)
功能:
*会暂时停止目前进程的执行,直到有信号来到或子进程结束
*参数:如果不在意结束状态值,则参数status可以设成NULL。
*参数pid为欲等待的子进程识别码:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。

参数option可以为0 或下面的OR 组合 WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
返回值:
如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。

进程退出
exit,_exit用于终止进程
区别:
_exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
exit:在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。
对比发现:exit 比 _exit 更安全

定义函数: void exit(int status);
函数说明:
exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。

定义函数: void _exit(int status);
函数说明
_exit()用来立刻结束目前进程的执行,并把参数status返回给父进程,并关闭未关闭的文件。
此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。

【嵌入式学习历程13】Linux进程控制编程_第1张图片

你可能感兴趣的:(学习记录)