zoukankan      html  css  js  c++  java
  • [Linux] 进程

    PCB概念

    每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体, 其中包含以下信息。

    • 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非
      负整数。
    • 进程的状态,有运行、挂起、停止、僵尸等状态。
    • 进程切换时需要保存和恢复的一些CPU寄存器。
    • 描述虚拟地址空间的信息。
    • 描述控制终端的信息。
    • 当前工作目录(Current Working Directory)。
    • umask掩码。
    • 文件描述符表,包含很多指向file结构体的指针。
    • 和信号相关的信息。
    • 用户id和组id。
    • 控制终端、Session和进程组。
    • 进程可以使用的资源上限(Resource Limit)。

    fork和exec是本章要介绍的两个重要的系统调用。fork的作用是根据一个现有的进程复
    制出一个新进程,原来的进程称为父进程(Parent Process),新进程称为子进程(Child
    Process)。

    进程原语: fork

    #include <unistd.h>
    pid_t fork(void);
    

    子进程复制父进程的0到3g空间和父进程内核中的PCB,但id号不同。
    fork调用一次返回两次.

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    
    int main()
    {
        pid_t pid;
        char *message;
        int n;
    
        pid = fork();
        if(pid < 0){
            perror("fork error!");
            exit(1);
        }else if(pid == 0){
            message = "This is the child
    ";
            n = 6;
        }else{
            message = "This is the parent
    ";
            n = 3;
        }
    
        for(; n > 0; n--){
            printf(message);
            sleep(1);
        }
    
        return 0;
    }
    

    进程相关函数

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t getpid(void);  //返回调用进程的PID号
    pid_t getppid(void); //返回调用进程父进程的PID号
    uid_t getuid(void);  //返回实际用户ID
    uid_t geteuid(void); //返回有效用户ID
    gid_t getgid(void);  //返回实际用户组ID
    gid_t getegid(void); //返回有效用户组ID
    

    进程原语: exec族

    用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
    子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
    用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
    新进程,所以调用exec前后该进程的id并未改变。
    其实有六种以exec开头的函数,统称exec函数:

    #include <unistd.h>
    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[]);
    

    这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错
    则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

    // 子进程创建成功后调用exex函数执行firefox程序
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    	pid_t pid;
    
    	pid = fork();
    
    	if(pid == 0){
    		execl("/usr/bin/firefox", "firefox", "www.baidu.com", NULL);
    	}
    	else if(pid > 0){
    		while(1){
    			printf("parent
    ");
    			sleep(1);
    		}
    	}
    	else{
    		perror("fork");
    		exit(1);
    	}
    	return 0;
    }
    

    进程原语: wait/waitpid

    僵尸进程: 子进程退出,父进程没有回收子进程资源(PCB),则子进程变成僵尸进程
    孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程变成1号
    进程init进程,称为init进程领养孤儿进程

    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);
    

    返回值:
    < -1 回收指定进程组内的任意子进程
    -1 回收任意子进程
    0 回收和当前调用waitpid一个组的所有子进程
    > 0 回收指定ID的子进程

    一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还
    保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止
    则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这
    些信息,然后彻底清除掉这个进程。

    如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时
    的进程状态称为僵尸(Zombie)进程。
    任何进程在刚终止时都是僵尸进程,正常情况下,僵
    尸进程都立刻被父进程清理了,为了观察到僵尸进程,我们自己写一个不正常的程序,父进
    程fork出子进程,子进程终止,而父进程既不终止也不调用wait清理子进程, 程序运行时在另一个终端输入ps aux可以看到处于僵尸状态的子进程.

    #include <unistd.h>
    #include <stdlib.h>
    
    int main(void)
    {
        pid_t pid = fork();
    
        if(pid < 0) {
            perror("fork");
            exit(1);
        }
        if(pid > 0) {/* parent */
            while(1);
        }
        /* child */
        
        return 0;
    }
    

    若调用成功则返回清理掉的子进程id,若调用出错则返回-1。父进程调用wait或waitpid时可能会:

    • 阻塞(如果它的所有子进程都还在运行)。
    • 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信
      息)。
    • 出错立即返回(如果它没有任何子进程)。

    这两个函数的区别是:

    • 如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如
      果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。
    • wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。

    可见,调用wait和waitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进
    程终止,起到进程间同步的作用。如果参数status不是空指针,则子进程的终止信息通过
    这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为
    NULL。

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main()
    {
    	pid_t pid;
    	pid = fork();
    
    	if(pid > 0){ // 当前处于父进程
    		while(1){
    			printf("I'm parent, parent id = %d
    ", getpid());
    			// 通过父进程回收子进程的PCB, wait返回值是子进程的ID号
    			// 直到回收完子进程才会继续执行父进程, wait是一个阻塞函数
    			printf("wait for child pid = %d
    ", wait(NULL));
    			sleep(1);
    		}
    	}else if(pid == 0){ // 子进程结束后变成僵尸进程
    		printf("I'm child, child id = %d
    ", getpid());
    		sleep(3);
    		// 用户空间已经释放掉了, 但是PCB没有释放, PCB要等待父进程回收
    	}else{
    		perror("fock");
    		exit(1);
    	}
    	return 0;
    }
    
    /* 运行结果
    $ ./a.out 
    I'm parent, parent id = 13351
    I'm child, child id = 13352
    [由于父进程的wait阻塞机制, 中间会等待3秒, 直到回收完子进程才会继续执行父进程]
    wait for child pid = 13352
    I'm parent, parent id = 13351
    wait for child pid = -1 [已经回收完子进程了, 返回-1]
    I'm parent, parent id = 13351
    wait for child pid = -1
    */
    
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        pid_t pid;
        pid = fork();
        if (pid < 0) {
            perror("fork failed");
            exit(1);
        }
        if (pid == 0) {
            int i;
            for (i = 3; i > 0; i--) {
                printf("This is the child
    ");
                sleep(1);
            }
            exit(3);
        } else {
            int stat_val;
            waitpid(pid, &stat_val, 0);
            if (WIFEXITED(stat_val))
            printf("Child exited with code %d
    ", WEXITSTATUS(stat_val));
            else if (WIFSIGNALED(stat_val))
            printf("Child terminated abnormally, signal %d
    ", WTERMSIG(stat_val));
        }
        return 0;
    }
    
    /*
    This is the child
    This is the child
    This is the child
    Child exited with code 3
    */
    
  • 相关阅读:
    jvm理论-运行时数据区
    java nio-理解同步、异步,阻塞和非阻塞
    真正的mybatis_redis二级缓存
    扩展 DbUtility (1)
    DbUtility v3 背后的故事
    DbUtility v3
    Jumony Core 3,真正的HTML引擎,正式版发布
    新项目,WebTest
    面试经验总结,每个求职者应该具有的职业素养
    mkdir()提示No such file or directory错误的解决方法
  • 原文地址:https://www.cnblogs.com/moon1992/p/7368393.html
Copyright © 2011-2022 走看看