08---进程控制

来源:互联网 发布:linux分頁地址 编辑:程序博客网 时间:2024/06/04 18:30

2017-05-07  23:14   未完待续

==========

进程段结构
代码段:可执行代码
数据段:常量,全局变量,静态变量   <----其中专门一部分存储未初始化的变量,称为bss
堆:动态分配的内存变量
栈:用于函数调用,保存函数的返回地址,存放着函数的参数,函数内部定义的局部变量
高地址:环境变量是形如 "name=value" 的字符串
 
-------------------
COW: copy on write 
早期的实现是在 fork 之后为子进程获得一个局进程数据段、堆栈段的副本,而和父进程共享代码段。由于常常在 fork 后执行 exec 调用,所以后期的实现引入了 COW,数据段 & 堆栈段 设置为共享且只读,只有在父进程或子进程试图在这些段做出修改时为其创建一个副本。
--------------
进程状态:
1.运行状态(R)
进程正在运行或者在运行队列中等待运行
2.可中断的等待状态(S)
进程正在等待某个事件完成(如等待数据到达)。等待过程可以被信号或定时器唤醒
3.不可中断的等待状态(D)
进程正在等待某个事件完成,在等待中不可以被信号或定时器唤醒,必须等待直到等待的事件发生。
4.僵死状态(Z)
进程已经终止,但进程描述符依然存在,直到父进程调用wait()函数后释放
5.停止状态(T)
进程因为收到SIGSTOP,SIGSTP,SIGTIN,SIGTOU信号后停止运行或者改进成正在被跟踪(调试程序时,进程处于被跟踪状态)
状态后缀:
< 高优先级进程
N 低优先级进程
L 内存锁页,即页不可以被换出内存
s 该进程为会话首进程
l 多线程进程
+ 进程位于前台进程组
--------------
进程控制:
1.创建进程
2.执行新进程
3.退出进程
4.改变进程优先级

几个与进程控制相关的系统调用:
fork()  创建一个新进程
exit()  终止进程
wait()  将父进程挂起,等待子进程终止
exec()  执行一个应用程序
getpid()  获取当前进程的ID
nice()  改变进程的优先级

=================
创建进程的两种方式
1.操作系统创建(如 init 进程)
2.父进程调用 fork() 创建
进程创建成功的话,fork() 函数返回两次值,子进程返回值为 0,父进程返回的值为新创建子进程的 PID
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

pid_t   pid;
int     var_global=10;    // 全局变量,存储在数据段

int main()
{
    int var_local=20;    // 局部变量,存储在栈
    pid=fork();
    if(pid<0)
        printf("Oh,failed\n");
    else if(pid==0)
    {   
        printf("This is child process.\n");
        var_global++;
        var_local++;
    }   
    else
    {   
        sleep(1);
        printf("This is parent process.\n");
        var_global--;
        var_local--;
    }   
    printf("var_global is:%d,var_local is:%d\n",var_global,var_local);
    exit(0);
}
-----结果如下-----
[root@os7 ch07]# ./test
This is child process.
var_global is:11,var_local is:21
This is parent process.
var_global is:9,var_local is:19    
由上面的例子可以知道父进程和子进程各有自己的 var_global 和 var_local 

关于 COW 有几点需要说明,不过在这之前先看下如下代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

pid_t   pid;
int     var_global=10;

int main()
{
    int var_local=20;
    pid=fork();
    if(pid<0)
        printf("Oh,failed\n");
    else if(pid==0)
    {   
        printf("This is child process.\n");
        var_local++;
        var_global++;
        printf("var_global is:%d,var_local is:%d\n",var_global,var_local);
        printf("var_global's address is: %x,var_local's address is:%x\n",&var_global,&var_local);
        exit(0); 
    }   
    else
    {   
        sleep(1);
        printf("This is parent process.\n");
        var_local--;
        var_global--;
        printf("var_global is:%d,var_local is:%d\n",var_global,var_local);
        printf("var_global's address is: %x,var_local's address is:%x\n",&var_global,&var_local);
        exit(0); 
    }   
}
-----结果如下-----
[root@os7 ch07]# ./test
This is child process.
var_global is:11,var_local is:21
var_global's address is:60104c,var_local's address is:b761cffc
This is parent process.
var_global is:9,var_local is:19
var_global's address is:60104c,var_local's address is:b761cffc

不是”写时复制“吗?为什么父进程和子进程中的变量值不一样,但变量地址却一样?因为这里的地址不是真正的物理地址,而是逻辑地址。关于逻辑地址--->线性地址--->物理地址 的概念和转换先不做研究。
fork() 之后父进程先运行还是子进程先运行?这个要看调度程序。但是很明显的是,如果父进程先运行,且出现了 COW,而子进程执行时立即调用 exec,这就导致父进程的 COW 没有意义,也就是说如果子进程先运行的话,父进程就没有必要 COW 了。
调用 vfork() 创建子进程会保证子进程先于父进程运行,直到子进程调用 exec 或 exit 后父进程才被恢复调度。由于 vfork() 是为搭配 exec ,所以 vfork() 不会将父进程的地址空间完全复制到子进程中,子进程在调用 exec 或 exit 之前它都在父进程空间中运行。所以我们对变量的操作,其实是对父进程空间变量的操作,代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

pid_t   pid;
int     var_global=10;

int main()
{
    int var_local=20;
    pid=vfork();
    if(pid<0)
        printf("Oh,failed\n");
    else if(pid==0)
    {   
        printf("This is child process.\n");
        var_local++;
        var_global++;
        _exit(0);    // 不执行标准I/O 缓冲区的冲洗操作
    }   
    else
    {   
        printf("This is parent process.\n");
        printf("var_global is:%d,var_local is:%d\n",var_global,var_local);
        exit(0);
    }   
}
-----结果如下-----
[root@os7 ch07]# !.
./test
This is child process.
This is parent process.
var_global is:11,var_local is:21
除了 fork()vfork(),还有 clone() 也可以创建子进程,man 文档说明如下:
Unlike fork(), clone() allows the child process to share parts of its execution context with the calling process, such as the memory space, the  table of file  descriptors,  and the table of signal handlers.
=================
孤儿进程 & 僵尸进程
孤儿进程,顾名思义就是父进程在子进程之前就挂了,这时候子进程就变成了子进程,由 init 托管,即父进程变为 init (CentOS 7 上是 systemd)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
    pid_t   pid;

    pid=fork();
    switch(pid)
    {   
        case -1:
            printf("Oh,fork failed.\n");
            exit(-1);
        case 0:
            while(1)
            {   
                printf("This is child process.My PPID is %d.\n",getppid());
                sleep(1);
            }   
        default:
            sleep(1);
            printf("This is parent process.My PID is %d.\n",getpid()); 
            exit(0);
    }   
}

和孤儿进程相反,僵尸进程的产生是因为子进程先于父进程退出。也许会有这样的疑问,子进程要退出就直接退出就完了,为什么操作系统还要定义一个僵尸进程呢?其实僵尸进程只是一个进程的状态,以子进程调用 exit() 退出为例,执行完 exit() 后该释放的东西都释放了,但是在 process table 中记录了它生前的一些信息,包括怎么死的。如果操作系统把所有与该进程相关的信息都销毁了,我们都不知道进程是怎么死的,这可不行!所以操作系统设计了相关的数据结构来存子进程生前信息,直到父进程通过 wait() 来收尸,父进程收尸完毕后,操作系统就会删掉该子进程的信息,每个进程或多或少都会经历僵尸阶段,即从它死掉到其父进程来收尸的这个时间段。下面来看下 wait() 和 waitpid()
-------------------------
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
-------------------------
如果所有子进程都还在运行中,则 wait() 阻塞,直到出现一个子进程退出。wait 返回退出的子进程的 PID,并将进程退出状态码写到其参数 *status 指向的整形变量中。如果要指定等待某个子进程,则可以使用 waitpid() ,




========================================如下内容待整理... ...



0 0