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\n",pid);
    printf("%d\n",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\r\n”);
    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\n");
        exit(1);

    }
    else if(ret == 0)
    {
        //child
        while(1)
        {
            sum ++;
            printf("child :%d\n",sum);
            sleep(1);
        }
    }
    else 
    {
        //father 
        while(1)
        {
            sum += 2;
            printf("father :%d\n",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\n", 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\n");
        exit(1);

    }
    else if(ret == 0)
    {
        //child
        printf("go to aaa.exe\n");
        execl("./aaa.exe","./aaa.exe",NULL);
        printf("back to child\n");
    }
    else 
    {
        //father 
        for(int i = 1;i != 10;i++)
        {
            sum += 2;
            printf("father :%d\n",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\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status)) /* 因信号异常退出,打印引起退出的信号 */
        printf("abnormal termination, signal number = %d\n", WTERMSIG(status));
    else 
        printf("other status\n"); /* 其它错误 */
}

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\n"; 
    len =strlen(buf); 
    pc=fork();     

    //第一步
    if(pc<0)
    { 
        printf("error fork\n"); 
        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); 
    } 
}

 

posted @ 2020-05-20 22:25  祁峰_1024  阅读(180)  评论(0编辑  收藏  举报