• Linux进程


    首先,什么是进程?

    在windows下打开任务管理器,会弹出这个界面:

     对,他们就是进程,正在运行的程序,就是进程。

    程序进程是不同的东西

    程序是写在磁盘中,进程是在内存中。进程指一个程序的执行过程。

    程序是静态的。进程是动态的,包含动态创建 调动 消亡的过程。

    进程状态:

    · 执行态:该进程正在,即进程正在占用 CPU。
    · 就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
    · 等待态:进程不能使用 CPU,若等待事件发生则可将其唤醒。


    查看进程

    ps

    ps -aux

    把进程状态的标志整理一下

    R:正在执行
    S:阻塞状态
    T:暂停状态
    Z:僵尸进程(不存在但暂时无法消除)
    D:不可中断的进程
    <:高于优先级的进程
    N:低于优先级的进程
    L:有内存分页分配并锁在内存中
    s 进程的领导者(在它之下有子进程);
    l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);
    + 位于后台的进程组
    ps -ef

     PPID父进程

    0号进程是调度进程,不执行任何程序,是内核的一部分,系统进程。

    1号进程是init,所有进程的祖先。init负责 引导系统 启动守护进程和运行必要的程序。他不是系统进程,但是以系统的超级用户特权运行。

    父进程主要负责子进程空间的清理。


    并发

      同时发生(宏观)

      看起来像同时发生,其实并不是。只是CPU运行速度很快。

    并行

      同时执行(微观)

      多个CPU真的可以同时执行不同的进程。

    同步

      宏观上同步,同的意思是协同,进程间协同有序进行,不是同时。

      一组进程为了协调推进速度,在某些地方需要等待或唤醒,这种进程之间的相互制约就叫进程同步

      进程之间需要通信。

    异步

      一个异步过程调用发出后,等到收到信息通知,再去执行,在此期间他在执行自己的事情。

    同步是两个进程相关的,一个进程需要阻塞等待另一个进程。

    异步是两个进程无关,各自执行各自的程序


    获取进程ID

    pid_t getpid(void);

    获取父进程ID

    pid_t getppid(void);

    返回值为进程ID号

    试验一下

    #include <unistd.h>
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        pid_t pid;
        pid_t ppid;
        pid = getpid();
        ppid = getppid();
        printf("%d
    ",pid);
        printf("%d
    ",ppid);
        while(1);
        return 0;
    }

    得出父进程和自身的pid。

    然后使用  ps -aux找到这两个进程ID

     父进程:终端

     循环本身的进程:


    创建进程

    pid_t fork(void);

    这个函数返回值有2个

    返回值为 -1 时说明进程创建失败

    在子进程中  返回值为0   

    在父进程中  返回值为子进程的pid

    执行fork()后,给父进程返回子进程的PID

    子进程获得父进程的数据空间、堆和栈的复制。

    父子进程并不是共享这些资源,他们分别拥有两份不同的空间。

    使用方法如下:

    pid_t ret;
    //位于fork前定义变量在父子进程中都存在,但彼此独立
    ret = fork();
    if(ret == -1)
    {
        perror(“fork fail
    ”);
        return -1;
    }
    else if(ret == 0)
    {
       //仅子进程执行代码
    }
    else
    {
       //仅父进程执行代码
    }

    这里说一下perror()

    该函数时用来返回错误信息,输入参数为你要打印的那句话的头,系统会自动为你补全失败原因。

    什么样的函数可以使用perror?

    我们 man fork 看一下 fork 里的详细信息

    能找到这样一个返回值信息

     上面明确写了,当返回值为-1时,会设置error。

    有这样说明的函数,才可以使用 perror() 来打印错误信息。

    使用示例:子进程每次加1 父进程每次加2

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    
    int main(int argc, char const *argv[])
    {
        pid_t ret;
        int sum;
    
    
        ret = fork();
    
        if(ret == -1)
        {
            perror("fork creat error
    ");
            exit(1);
    
        }
        else if(ret == 0)
        {
            //child
            while(1)
            {
                sum ++;
                printf("child :%d
    ",sum);
                sleep(1);
            }
        }
        else 
        {
            //father 
            while(1)
            {
                sum += 2;
                printf("father :%d
    ",sum);
                sleep(1);
            }
        }
    
        exit(0);
    }

    运行结果如下:

    再生个儿子:

    int main(int argc, char const *argv[])
    {
        int a = 100;
        int n = fork();
    
        if (n < 0)
        {
            //错误退出
        }
        else if (n == 0)
        {
            //子进程1
        }
        else
        {
            //父进程
            int m = fork();
            if(m < 0)
            {
                //错误
            }
            else if(m == 0)
            {
                //子进程2
            }
            else
            {
                //父进程代码
            }
    
        }
        return 0;
    }

    创建进程还有一个函数  vfork()

    fork()  vfork() 的区别:

    1.父子进程调度:
    使用fork(),父子进程先后运行顺序是不固定的,当其中一个进程阻塞后会调度另一个进程运行。
    使用vfork(),一定是先运行子进程,等待子进程运行结束后再运行父进程,
    在子进程运行期间内即使子进程进入阻塞态也不会运行父进程。

    2.进程空间:
    使用fork(),子进程会拷贝 在fork()函数前 父进程的空间,且彼此独立
    使用vfork(),父子进程共享空间(vfork()函数之前的缓存空间共用),
    子进程退出运行时需要使用exit(0)等方法退出,而不是使用return
    (如果直接以return结束子进程,由于父子进程共享空间,导致父进程的堆栈受到影响,父进程中的数据发生错乱)

    简单一点:

        fork:子进程拷贝父进程的数据段
    
        vfork:子进程与父进程共享数据段
    //--------------------------------------------------
    
        fork:父、子进程的执行次序不确定
    
        vfork:子进程先运行,父进程后运行

    我让他循环5次,然后把创建进程的函数换成这个,得到现象如下:

     销毁进程

    常见终止进程的方式:

    主动:
    1、在主函数中 return,注意return 只是结束函数,而如果主函数都结束了,那进程肯定就结束了。

    2、exit()       标准函数      <stdlib.h>

    3、_exit()    系统调用函数    <unistd.h>

    被动:

    4、接收到某个信号  比如CTRL+C

    5、kill

    进程替换

    可以理解为 鸠占雀巢

    在 Linux 中使用 exec 函数族主要有两种情况:
    · 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何 exec 函数族 让自己重生;
    · 如果一个进程想执行另一个程序,那么它就可以调用 fork 函数新建一个进程,然后调用任何一个 exec,这样看起来就好像通过执行应用程序而产生了一个新进程。(这种情况非常普遍)

    比如我现在正在执行一个 test.exe 的程序,程序代码中有一个 进程替换的函数(exec) 参数为 qq,那么就切换为执行qq 不再执行test.exe了

    但是进程的 PID还是原来的 PID,换的是程序。

    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 execvpe(const char *file, char *const argv[],char *const envp[]); 

    第一个参数放文件名/带路径的文件名

    根据函数名字找一下规律,exec的后缀

    l     v  表示参数呈现形式

    l list  列出参数列表

    v   vector   参数用数组储存起来

    p  path     可以使用路径

    e  environ   参数中有环境数组

    如果成功执行,函数就不回来了。。。

    如果执行失败,返回值为 -1.

    最后一个参数,必须以空指针 NULL 作为结束。

    可以利用exec替换程序的特性,将子进程替换为要运行的程序,实现父子进程并发运行,并且各自的空间不受干扰

    int ret = fork();
    if(ret == 0)
    {
        //子进程
        execl(/*替换执行新程序*/);
    }
    else{
        //父进程
    }

     试验一下:
    先写一个子进程中”重生”的程序:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char const *argv[])
    {
        int sum = 0;
        while(1)
        {
            printf("aaa.exe : %d
    ", sum++);
            sleep(1);
        }
        return 0;
    }

    然后写主函数:

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    
    int main(int argc, char const *argv[])
    {
        pid_t ret;
        int sum = 0;
    
    
        ret = fork();
    
        if(ret == -1)
        {
            perror("fork creat error
    ");
            exit(1);
    
        }
        else if(ret == 0)
        {
            //child
            printf("go to aaa.exe
    ");
            execl("./aaa.exe","./aaa.exe",NULL);
            printf("back to child
    ");
        }
        else 
        {
            //father 
            for(int i = 1;i != 10;i++)
            {
                sum += 2;
                printf("father :%d
    ",sum);
                sleep(1);
            }
        }
    
        exit(0);
    }

    运行结果如下:

     

     可以看出并没有打印出 back to child 这句话,证明程序走exec走了之后就没有回来

     等待进程

    如同我们刚才做的实验一般,父子进程分别做自己的事情,并发 、异步,那么父子进程肯定都有执行完任务的时候

    那么问题就来了。我们前面提过,父进程的责任之一就是处理子进程的终止。如果子进程先执行完了任务,父进程没有等待,那么子进程就变成了僵尸进程,父进程不收尸,那么已经结束的子进程就会一直占用资源。如果是父进程先于子进程完成任务,父进程自己exit()了,那么子进程就变成了孤儿进程,被祖先进程 init 收养。

    头文件

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

    参数是保存子进程退出状态的指针

    可以用下表的宏来查看退出状态:

    返回值:

      成功返回获取到退出状态进程的PID (退掉的子进程)

      失败返回 -1

    看个例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    void print_exit_status(int status) 
    { /* 自定义打印子进程退出状态函数 */
        if (WIFEXITED(status)) /* 正常退出,打印退出返回值 */
            printf("normal termination, exit status = %d
    ", WEXITSTATUS(status));
        else if (WIFSIGNALED(status)) /* 因信号异常退出,打印引起退出的信号 */
            printf("abnormal termination, signal number = %d
    ", WTERMSIG(status));
        else 
            printf("other status
    "); /* 其它错误 */
    }
    
    int main(int argc, char *argv[]) 
    {
        pid_t pid;
        int status;
        if ((pid = fork()) < 0) 
        { /* 创建子进程 */
            perror("fork error");
            exit(-1);
        } 
        else if (pid == 0) 
        {
            exit(7); /* 子进程调用 exit 函数,参数为 7 */
        }
    
        if (wait(&status) != pid) 
        { /* 父进程等待子进程退出,并获取退出状态*/
            perror("fork error");
            exit(-1);
        }
    
        print_exit_status(status); /* 打印退出状态信息 */
    
        if ((pid = fork()) < 0) 
        { /* 创建第二个子进程 */
            perror("fork error");
            exit(-1);
        } 
        else if (pid == 0) 
        {
            abort(); /* 子进程调用 abort()函数异常退出 */
        }
    
        if (wait(&status) != pid) 
        { /* 父进程等待子进程退出,并获取退出状态*/
            perror("fork error");
            exit(-1);
        }
        print_exit_status(status); /* 打印第二个退出状态信息 */
        return 0;
    }

    运行结果如下:

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

    参数:

    pid      要等待的进程pid

            或者:

              -1     表示等待任意一个子进程,这样与wait等效

              0      等待 与调用进程处于同一进程组 的进程

              <-1   等待进程组的所有子进程

    status      和上面wait一样

    options        0 或者 下列选项

     简单的说 ,waitpid可以做到:1、等待一个特定的子进程。2、非阻塞获得子进程的状态

     刚才我们使用过的 

    wait (&status);

    等效于

    waitpid (-1, &status, 0);

    这里说一下阻塞式非阻塞式

    引用一个故事:

    老张爱喝茶,废话不说,煮开水。
    出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
    1 老张把水壶放到火上,立等水开。(同步阻塞
    老张觉得自己有点傻
    2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞
    老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
    3 老张把响水壶放到火上,立等水开。(异步阻塞
    老张觉得这样傻等意义不大
    4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞
    老张觉得自己聪明了。


    所谓同步异步,只是对于水壶而言
    普通水壶,同步;响水壶,异步。
    虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
    同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

    所谓阻塞非阻塞,仅仅对于老张而言
    立等的老张,阻塞;看电视的老张,非阻塞。
    情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

    在这里呢,阻塞式,就是父进程一直等,等到子进程有返回为止。

    就像老张等谁开一样,不高效。非阻塞就是隔一会父进程去看看子进程完事了没。

    那么我们如何修改参数,让wait实现阻塞非阻塞的效果?

    waitpid(pid,status,WNOHANG);

    意思就是每隔一小段时间,子进程都会返回一个值,直到有一次返回的值是子进程pid,证明子进程死亡。

    补充前面提过的一个概念:进程组

    每个进程,有属于自己的组,就是他所在的进程组。

    进程组和进程一样,有自己的ID

    可以这样获取进程组ID

    //返回值为调用该函数的进程的进程组ID
    pid_t getpgrp(void);

    每一个进程组,有一个进程组组长,进程组的ID,就是组长的ID

    int setpgid(pid_t pid, pid_t pgid);

    将pid 进程的进程组ID设置为 pgid。如果这两个参数相等,是将pid任命为进程组组长。

     再fork后调用该函数

    父进程设置其子进程的进程组ID,然后子进程自己设置自己的进程组ID

    这样可以保证父子进程在同一个进程组。

    既然有了进程组,就要提一下对话期的概念

     对话期 是一个或多个进程组的集合。

     建立一个对话期

    pid_t setsid(void);

    成功返回进程组 ID

    失败返回 -1

    (1) 此进程变成该新对话期的对话期首进程(session leader,对话期首进程是创建该对话期的进程)。此进程是该新对话期中的唯一进程。
    (2) 此进程成为一个新进程组的组长进程。新进程组I D是此调用进程的进程ID。
    (3) 此进程没有控制终端(下一节讨论控制终端)。如果在调用setsid之前此进程有一个控制终端,那么这种联系也被解除。

    如果调用该函数的进程不是进程组组长,才会创建一个新对话期。

    如果是组长,则返回出错。

    守护进程

    Daemon进程,后台服务进程。

    下面是一个创建守护进程的方法。

     代码如下:

    /*dameon.c 创建守护进程实例*/ 
    #include<stdio.h> 
    #include<stdlib.h> 
    #include<string.h> 
    #include<fcntl.h> 
    #include<sys/types.h> 
    #include<unistd.h> 
    #include<sys/wait.h> 
    #define MAXFILE 65535 
    int main() 
    { 
        pid_t pc; 
        int i,fd,len; 
        char *buf="This is a Dameon
    "; 
        len =strlen(buf); 
        pc=fork();     
    
        //第一步
        if(pc<0)
        { 
            printf("error fork
    "); 
            exit(1); 
        }
        else if(pc>0) 
            exit(0); 
    
        /*第二步*/ 
        setsid(); 
    
        /*第三步*/ 
        chdir("/"); 
    
        /*第四步*/ 
        umask(0); 
    
        /*第五步*/ 
        for(i=0;i<MAXFILE;i++) 
            close(i); 
    
        /*这时创建完守护进程,以下开始正式进入守护进程工作*/ 
        while(1)
        { 
            if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
            { 
                perror("open");
                exit(1); 
            } 
            write(fd, buf, len+1); 
            close(fd); 
            sleep(10); 
        } 
    }
  • 相关阅读:
    2019 上海网络赛 J stone name (01背包)
    CodeForces
    2019 年百度之星·程序设计大赛
    CodeForces
    POJ 3728 The merchant (树形DP+LCA)
    HihoCoder
    HihoCoder 1055 刷油漆 (树上背包)
    HI3518E平台ISP调试环境搭建
    有用的调节
    【HI3520DV200】sample
  • 原文地址:https://www.cnblogs.com/qifeng1024/p/12926736.html
Copyright © 2020-2023  润新知