C语言并发程序设计

进程的概念

  程序:

    存放在磁盘上的指令和数据的有序集合(文件)

    静态的

  进程:

    执行一个程序所分配的资源的总称

    进程是程序的一次执行过程

    动态的,包括创建、调度、执行和消亡

进程包含的内容

  进程包含:正文段(代码段)、用户数据段、系统数据段

  程序包含:正文段(代码段)、用户数据段

  系统数据包含:进程控制块、CPU寄存器值、堆栈

    进程控制块(PCB)包含:

      进程标识PID

      进程用户

      进程状态、优先级

      文件描述符表

    CPU寄存器值:

      PC:program counter, 记录着下一条执行指令的地址

    堆栈:所有的局部变量都是在栈中存在的

进程的类型

  交互进程:在shell下启动。可以在前台运行,也可以在后台运行

  批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行

  守护进程:和终端无关,一直在后台运行 

进程的状态

  运行态:进程正在运行,或者准备运行

  等待态:进行在等待一个事件的发生或某种系统资源,又分为可中断和不可中断

  停止态:进程被中止,收到信号后可继续运行

  死亡态:已终止的进程,但pcb没有没有被释放

查看进程信息

  ps:查看系统进程快照

    ps -ef : 查看系统中所有的进程信息

    ps aux: 比ps -ef 多一个当前的进程状态信息,进程优先级

  top:查看进程动态信息,每个3秒刷新一次

  /proc:查看进程详细信息,在proc目录下以进程PID为名的目录,其中status文件保存着进程的详细信息,fd目录保存这使用的文件

改变进程优先级

  nice: 按用户指定的优先级运行进程,nice的区间[-20, 19],默认值为0,值越小优先级越高,普通用户能够设置的最小值为0。

    nice -n 2 ./a.out

  renice:改变正在运行进程的优先级,普通用户只能增加这个值。

    renice -n 2 PID

前后台进程切换

  jobs:查看后台进程

    xdl@xdl-gj:~/C语言/thread$ ./a.out &

    [1] 22441 //1表示作业号

    xdl@xdl-gj:~/C语言/thread$ ./a.out &

    [2] 22442

    xdl@xdl-gj:~/C语言/thread$ jobs

    [1]- 运行中 ./a.out &

    [2]+ 运行中 ./a.out &

  bg:降挂起的进程在后台运行

  fg:把后台运行的进程放到前台运行

    xdl@xdl-gj:~/C语言/thread$ fg 1

    ./a.out

    ^Z // ctrl +z 使进程挂起

    [1]+ 已停止 ./a.out

    xdl@xdl-gj:~/C语言/thread$ bg 1

    [1]+ ./a.out &

创建进程

  fork函数用于创建一个进程

    #include <unistd.h>

    pid_t fork(void);

    创建新的进程,失败时返回-1

    成功时父进程返回子进程的进程号,子进程返回0

    通过fork的返回值区分父进程和子进程

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


int main(int argc, char *argv[])
{
    pid_t pid;
    pid = fork();

    printf("p, pid);

    if (pid < 0)
    {
        perror("fork");
        return -1;
    }    
    else if (pid == 0)
    {
        printf("child process: my pid is %d\n", getpid());
    }
    else
    {
        printf("parent process: my pid is %d\n", getpid());
    }
    return 0;
}
/*
pid=6018
parent process: my pid is 6017
pid=0
child process: my pid is 6018

*/

父子进程

  子进程继承父进程的内容

  父子进程有独立的地址空间,互不影响

  若父进程先结束

    子进程称为孤儿进程,被init进程收养(linux启动内核之后自动创建的用户态进程PID为1)

    子进程变成后台进程

  若子进程先结束

    父进程如果没有及时回收,子进程变成僵尸进程

  子进程从何处开始运行?

    从fork下一句指令开始,并没有执行fork语句

  父子进程谁先执行?

    不一定,一般父进程创建子进程后时间片没有用完则继续执行

  父进程能否多次调用fork?子进程呢?

    可以

结束进程

  exit/_exit

    #include <stdlib.h>

    #include <unistd.h>

    void exit(int status);

    void _exit(int status);

    结束当前进程并将status(低八位)返回

    exit结束结束进程时会刷新(流)缓冲区

#include <stdio.h> 
#include <stdlib.h> // for exit
#include <unistd.h>  //for _exit

int main(int argc, char *argv[])
{
    printf("this process will exit");
    //exit(0);//会打印上一句
    _exit(0);//不会打印
    printf("never be displayed");
    return 0;
}

exec函数族

  进程调用exec函数执行某个程序

  进程当前内容被指定的程序替换

  实现让父子进程执行不同的程序

    父进程创建子进程

    子进程调用exec函数族

    父进程不受影响

  execl/execlp

    #include <unistd.h>

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

    int execlp(const char *file, const char *arg, ...);

EOF

    path 执行的程序的名称,包含路径

    arg... 传递给执行的程序的参数列表

    file执行的程序的名称,在PATH中查找

    执行ls命令,显示/etc目录下所有文件的详细信息

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


int main(int argc, char *argv[])
{
    pid_t pid;
    pid = fork();

    printf("p, pid);

    if (pid < 0)
    {
        perror("fork");
        return -1;
    }    
    else if (pid == 0)
    {
        printf("child process: my pid is %d\n", getpid());
        if (execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL) < 0)
        {
            perror("execl");
            return -1;
        }
    }
    else
    {
        printf("parent process: my pid is %d\n", getpid());
        if (execlp("ls", "ls", "-al", "/etc", NULL) < 0 )
        {
            perror("execlp");
            return -1;
        }
    }
    return 0;
}
/*
pid=6018
parent process: my pid is 6017
pid=0
child process: my pid is 6018

*/

  execv/execvp

    #include <unistd.h>

    int execv(const char *path, char *const argv[]);

    int execvp(const char *file, char *const argv[]);

    成功时执行指定的程序;失败时返回EOF

    arg...封装成指针数组的形式

    char *arg[] = {"ls", "-a", "-l", "/etc", NULL};

    if(execv("/bin/ls", arg) < 0){

      perror("execv");

    }

    if(execvp("ls", arg) < 0){

      perror("execvp");

    }

system

  #include <stflib.h>

  int system(const char *command);

  成功时返回命令command的返回值;失败时返回EOF

  当前进程等待command执行结束后才继续执行

进程回收

  子进程结束时由父进程回收

  孤儿进程由init进程回收

  若没有及时回收会出现僵尸进程

  wait函数

    #include <unistd.h>

    pid_t wait(int *status);

    成功时返回回收的子进程的进程号;失败时返回EOF

    若子进程没有结束,父进程一直阻塞

    若有多个子进程,哪个先结束就先回收

    status指定保存子进程返回值和结束方式的地址

    status为NULL表示直接释放子进程PCB,不接收返回值

  wait pid函数

    #include <unistd.h>

    pid_t waitpid(pid_t pid, int *status, int option);

    成功时返回回收的子进程的pid或0(表示子进程还没有结束);失败时返回EOF

    pid可用于指定回收哪个子进程或任意子进程(-1表示任意子进程)

    status指定用于保存子进程返回值和结束方式

    option指定回收方式,0(阻塞方式)或WNOHANG(非阻塞)

进程返回值和结束方式

  子进程通过exit/_exit/return 返回某个值(0-255)

  父进程调用wait(&status)回收

  WIFEXITED(status) : 判断子进程是否正常结束

  WEXITSTATUS(status): 获取子进程返回值

  WIFSIGALED(status): 判断子进程是否被信号结束

  WTERMSIG(status): 获取结束子进程的信号类型

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int status;
    pid_t pid;

    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        sleep(1);
        exit(2);
    }
    else
    {
        wait(&status);
        printf("%d\n", status);
    }
    return 0;
}
/*
200
521
0010 0000 0000
低7为(0~7)表示进程结束的类型,0表示正常结束,非零表示信号结束
高8为(8~15)表示子进程正常结束时的返回值
*/

守护进程

  守护进程(Daemon)是Linux三种进程类型之一

  通常在系统启动时运行,系统关闭时结束

  Linux系统中大量使用,很多服务程序以守护进程形式运行

守护进程的特点

  始终在后台运行

  独立于任何终端

  中期性的执行某种任务或等待处理特定事件

会话、控制终端

  Linux以会话(session)、进程组的方式管理进程

  每一个进程属于一个进程组,父子进程属于同一个进程组

  会话是一个或多个进程组的集合。通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话

  终端关闭时,所有相关进程会被结束

创建守护进程

  1、创建子进程,父进程退出

    if (fork() > 0){

      exit(0);

    }

    子进程变成孤儿进程,被init进程收养

    子进程在后台运行

  2、子进程创建新会话

    if (setsid() < 0 ){

      exit(-1);

    }

    子进程成为新的会话组长

    子进程脱离原先的终端

  3、更改当前工作目录

    chdir("/");

    chdir("/tmp");

    守护进程一直在后台运行,其工作目录不能被卸载

    重新设定当前工作目录cwd

  4、重设文件权限掩码

    if (umask(0) < 0){

      exit(-1);

    }

    文件权限掩码设置为0

    值影响当前进程

  5、关闭打开的文件描述符

    int i;

    for (i = 0; i < getdtablesize(); i++){

      close(i);

    }

    关闭所有从父进程继承的打开文件

    已脱离终端,stdio,stdout,stderr无法在使用

创建守护进程,每隔1秒将系统时间写入文件time.log

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h> // for umask()

int main(int argc, char *argv[])
{
    pid_t pid;
    FILE *fp;
    time_t t;
    int i;

    if ((pid = fork()) < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid > 0)
    {
        exit(0);
    }
    setsid();
    umask(0);
    chdir("/tmp");
    for (i = 0; i < getdtablesize(); i++)
    {
        close(i);
    }
    if ((fp = fopen("time.log", "a")) == NULL)
    {
        perror("fopen");
        exit(-1);
    }
    while (1)
    {
        time(&t);
        fprintf(fp, "%s", ctime(&t));
        fflush(fp);
        sleep(1);
    }
    return 0;
}