第二十七篇,LINUX系统编程开篇,进程的函数接口,包括创建进程fork(),查看进程的ID号getpid(),gedppid(),主动回收资源wait(),进程的退出exit() 函数。

一、linux系统编程学习大纲

1、进程的概念、进程的诞生与死亡、进程基本函数接口,进程意义。
2、进程之间的通信方式:无名管道、有名管道、信号、消息队列、共享内存(信号量)
3、linux下信号集概念、信号集的函数接口、信号集作用、如何给信号集设置阻塞状态?
4、线程的概念、线程与进程有什么区别?线程诞生与死亡、线程一系列函数接口。
5、线程同步互斥方式:有名信号量、无名信号量、互斥锁、读写锁。
6、处理空闲线程的方式:条件变量。
7、线程池  -> 为了能够同时处理多个任务。

二、进程的概念

1、什么是程序?什么是进程?
程序就是一堆待执行的代码。   --> 静态的文本数据。(代码)  -->  存放在硬盘中,客观存在
例如:
project.c   -> C语言"程序"
project     -> 可执行"程序"

特点:关机重启之后,文件依然还在。

进程就是当程序被CPU加载时,根据程序中每一行代码做出相应的效果,形成一个动态的过程,那么这个过程就是进程。   --> 动态的过程。(相当于正在热播的电视剧) , 存放在内存中
例如:
./project   --> 形成了一个动态的过程(进程)
关机重启之后,进程不在。

2、在linux下,如何开启一个进程?
直接在终端上执行一个程序,就可以开启一个进程。

gec@ubuntu:~$ ./bin/project   --> 就会开启一个名字叫./bin/project的进程。

3、当进程开启后,系统为进程分配什么资源?
1)会分配进程对应的内存空间。
-------------project.c-----------
int main()
{
    int x;
    return 0;
}
---------------------------------
当这个程序编译之后,运行时,系统就会在运行内存中分配4个字节给它。

2)当进程开启之后,系统会为进程分配一个任务结构体,这个任务结构体就是专门用于描述这个进程的。
struct task_struct{}
路径:/usr/src/linux-headers-4.10.0-38/include/linux/sched.h

三、关于查看进程信息的几个基本命令

1、查看整个系统中所有进程的ID号。   -->  ps -ef(数据是静态)

              PID    PPID
gec        6781   2769  0 10:00 pts/20   00:00:00 bash
root          1      0  0 Jan11 ?        00:00:02 /sbin/init auto noprompt   --> 祖先进程

PID:自身的ID号。   bash进程自身的ID号是6781
PPID:父进程的ID号。bash进程父进程ID号是2769

2、查看进程CPU使用率。   -->  top(数据是动态,每隔3s就会更新一次)

gec@ubuntu:~$ top   -> 按'q'返回终端。

Tasks: 240 total,   1 running, 174 sleeping,   0 stopped,   0 zombie
                   运行态        睡眠态        暂停态       僵尸态

                         %CPU
  1189 root      20   0  485500  58380  19816 S  3.7  5.9   0:35.88 Xorg                                    
  2769 gec       20   0  637344  40860  25880 S  3.3  4.1   0:09.01 gnome-terminal- 


3、查看整个系统所有进程之间的关系?   --> pstree

       PID    PPID

root          1      0  0 Jan11 ?        00:00:02 /sbin/init auto noprompt 
root       1172      1  0 Jan11 ?        00:00:00 /usr/sbin/lightdm
root       1478   1172  0 Jan11 ?        00:00:00 lightdm --session-child 12 19
gec        2118   1478  0 Jan11 ?        00:00:00 /sbin/upstart --user
gec        2769   2118  0 Jan11 ?        00:00:10 /usr/lib/gnome-terminal/gnome-terminal-server     ---> 终端
gec        2774   2769  0 Jan11 pts/18   00:00:00 bash                                              ---> 命令行
gec        6915   2774  0 10:35 pts/18   00:00:00 ps -ef                                            ---> linux命令

gec@ubuntu:~$ pstree
systemd─lightdm─lightdm──upstart─gnome-terminal──bash──pstree

四、进程诞生与死亡。

进程的生老病死.jpg

1、什么是僵尸态?
进程结束时候,就会从运行态变成僵尸态,僵尸态其实就是代表这个进程占用的资源没有释放掉,那么这个状态就称之为僵尸态。

2、总结几个易错点?
1)进程在暂停态,如果收到继续的信号,那么就会切换到就绪态,而不是运行态。
2)程序中main函数执行了return 0就代表一个进程的结束,一定会变成僵尸态。
3)进程不可以没有父进程,也不能同时拥有两个父进程。
4)孤儿进程特征:失去父亲时,会马上寻找继父,而不是等到孤儿进程变成僵尸态之后再找。
5)祖先进程一定会帮其他进程回收资源。

五、进程意义?

目前而言,我们写的代码都是一行一行代码地去执行,这种程序称之为单进程程序。
但是学习进程目的是为了学习多进程编程,可以实现同一个时刻处理两件/多件事情。


六、进程的函数接口?

单进程程序   --> 只能一行一行代码地去执行。
多进程程序   --> 同时执行两行代码   --> 让本来的进程产生一个新的子进程,让子进程去处理另外一件事情。

1、如何在正在运行的进程中创建一个子进程?   -->  fork()   --> man 2 fork
功能: fork - create a child process
    //创建一个子进程

使用格式:
    #include <unistd.h>

        pid_t fork(void);

参数:无
返回值:pid_t就是进程ID号的数据类型

    成功:会产生一个子进程
          父进程  --> 返回子进程的ID号(x>0)
          子进程  --> 0

    失败:不会产生子进程
          父进程  --> -1

  
    例题1: 在进程内部创建一个子进程,看看会不会同时做两件事情。

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

int main(int argc,char *argv[])
{
    //1. 目前而言,依然是一个单进程程序
    printf("helloworld!\n");
    printf("appletree!\n");
    
    //2. 在当前的进程情况下,创建一个子进程
    fork();
    
    //3. 目前而言,就已经有两个进程了,一个是父进程,一个是子进程
    printf("guangzhou!\n");
    
    return 0;
}

运行结果1:(父进程先运行)
gec@ubuntu:/mnt/hgfs/code$ ./demo1
helloworld!
appletree!
guangzhou!   --> 父进程打印
gec@ubuntu:/mnt/hgfs/code$ guangzhou!  --> 子进程打印


运行结果2:(子进程先运行)
gec@ubuntu:/mnt/hgfs/code$ ./demo1
helloworld!
appletree!
guangzhou!   --> 子进程打印
guangzhou!   --> 父进程打印
gec@ubuntu:/mnt/hgfs/code$ 

结论:
1)父子进程执行顺序是随机的。
2)fork之后的代码,两个进程都会执行。


    例题2: 想确保子进程先运行,做法:就是让父进程先睡眠,子进程不用睡眠。
        意味着父子进程工作内容不一样。

大概分析结果:
   pid_t x = fork();
   if(x > 0)   //父子进程都会判断,但是父进程能进去,子进程不能进去
   {
       //父进程工作内容
   }

   if(x == 0)  //父子进程都会判断,但是父进程不能进去,子进程能进去
   {
       //子进程工作内容
   }
-----------------------------------------------------

代码实现:

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

int main(int argc,char *argv[])
{
    //1. 目前而言,依然是一个单进程程序
    printf("helloworld!\n");
    printf("appletree!\n");
    
    //2. 在当前的进程情况下,创建一个子进程
    pid_t x = fork();
    
    //3. 目前而言,就已经有两个进程了,一个是父进程,一个是子进程
    if(x > 0)  //父进程才能进去
    {
        usleep(5000);
        printf("parent print hello!\n");
    }
    
    if(x == 0)  //子进程才能进去
    {
        printf("child print hello!\n");
    }
        
    return 0;
}

-----------------------------------------------------

    练习1: 写一个程序,使得子进程每隔1s就打印一次apple(一共10次),父进程每隔2s就打印一次hello(一共5次)。

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

int main(int argc,char *argv[])
{
    pid_t x;
    int i;
    x = fork();
    
    if(x > 0) //父进程
    {
        for(i=0;i<5;i++)
        {
            printf("parent hello!\n");
            sleep(2);
        }
    }
    
    if(x == 0) //子进程
    {
        for(i=0;i<10;i++)
        {
            printf("child apple!\n");
            sleep(1);
        }
    }
    
    return 0;
}


2、在一个进程中查看自己的PID号以及父进程的PID号。
          getpid()       getppid()   --> man 2 getpid

功能: getpid, getppid - get process identification
            //获取进程的ID号

使用格式:
    #include <sys/types.h>
        #include <unistd.h>

        pid_t getpid(void);
        pid_t getppid(void);

参数:无
返回值:
    成功:
        getpid()返回自己的PID号。
        getppid()返回父进程的PID号。

    失败: 不可能失败!

    练习2: 写一个程序,在子进程中输出自己和父进程的ID号,在父进程中输出自己和子进程的ID号。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    pid_t x;
    x = fork();
    
    if(x > 0)  //父进程
    {
        sleep(1);
        //输出自己的PID号
        printf("parent pid = %d\n",getpid());
        
        //输出子进程的PID号
        printf("child pid = %d\n",x);
    }
    
    if(x == 0)  //子进程
    {
        
        //输出自己的PID号
        printf("child pid = %d\n",getpid());
        
        //输出父进程的PID号
        printf("parent pid = %d\n",getppid());
    }
    
    
    return 0;
}

练习3: 验证孤儿进程是失去父亲时马上寻找继父,而不是等到自己变成僵尸再找。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc,char *argv[])
{
    pid_t x;
    x = fork();
    
    if(x > 0)
    {
        sleep(3);
        printf("parent exit!\n");
    }
    
    if(x == 0)
    {
        sleep(1);
        printf("my parent = %d\n",getppid());  //旧
        sleep(2);
        usleep(500000);
        printf("my parent = %d\n",getppid());  //新
        sleep(2);
        printf("my parent = %d\n",getppid());  //新
    }
    
    return 0;
}

七、如果父进程还存在的情况下,如何解决僵尸问题?

1、父进程还在,还主动回收资源。  --> wait()   --> man 2 wait
功能: wait for process to change state
    //等待一个进程,并且改变这个进程状态  (把进程从僵尸态变成死亡态)

使用格式:
    #include <sys/types.h>
        #include <sys/wait.h>

       pid_t wait(int *status);

参数:
    status:监听子进程的退出值
        填NULL   例如: wait(NULL)   --> 代表父进程主动回收资源,但是不关心子进程的退出状态。
          不填NULL   例如: int state;
                wait(&state);--> 代表父进程主动回收资源,而且还很关心你的退出状态。


wait():属于一个阻塞函数
如果子进程还在运行态,那么这个函数就会阻塞,一直阻塞等待到子进程变成僵尸为止。
如果子进程已经是僵尸态,那么这个函数就会直接返回。

返回值:
    成功:回收资源的那个子进程的ID号
    失败:-1


例题2: 验证一下wait函数是可以帮子进程回收资源的。

情况一:父进程时间长,子进程时间短。

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

int main(int argc,char *argv[])
{
    pid_t x;
    x = fork();
    if(x > 0)
    {
        printf("parent sleep...\n");
        sleep(8);
        wait(NULL);
        printf("parent after wait\n");
    }
    
    if(x == 0)
    {
        printf("child sleep...\n");
        sleep(3);
        printf("child exit!\n");
        
    }
    
    return 0;
}

情况二:父进程时间短,子进程时间长。

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

int main(int argc,char *argv[])
{
    pid_t x;
    x = fork();
    if(x > 0)
    {
        printf("parent sleep...\n");
        sleep(3);
        wait(NULL);
        printf("parent after wait\n");
    }
    
    if(x == 0)
    {
        printf("child sleep...\n");
        sleep(8);
        printf("child exit!\n");
        
    }
    
    return 0;
}


2、父进程还在,但是不主动回收资源。
只能等到父进程结束之后,再寻找继父帮自己回收资源。


    练习4: 验证以上的这种情况。

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

int main(int argc,char *argv[])
{
    pid_t x;
    x = fork();
    
    if(x > 0)
    {
        sleep(10);
        printf("parent exit!\n");
    }
    
    if(x == 0)
    {
        sleep(3);
        printf("child exit!\n");   //0~3 无僵尸  3~10 有僵尸  10之后 无僵尸
    }
    
    return 0;
}


八、进程的退出。   --> exit() / _exit() / _Exit()

这三个函数功能都是可以让进程退出,并且可以表示自己的退出状态,并传递给父进程。

exit(): 先清洗缓冲区,再退出。    -->  man 3 exit
功能: cause normal process termination
    //导致一个普通的进程结束

使用格式:
    #include <stdlib.h>

       void exit(int status);

参数:
    status:子进程的退出值
        0  -> 正常退出
           非0 -> 异常退出
返回值:无
  

_exit() / _Exit(): 不会清洗缓冲区,直接退出。   -->  man 2 _exit
功能: _exit, _Exit - terminate the calling process
            //结束正在运行的进程

使用格式:
    #include <unistd.h>

       void _exit(int status);

        #include <stdlib.h>

       void _Exit(int status);
        
参数:
    status:子进程的退出值
        0  -> 正常退出
           非0 -> 异常退出
返回值:无
1、缓冲区问题。

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

int main(int argc,char *argv[])
{
    printf("hello");
    exit(0);  //会清洗缓冲区    会打印hello
    _exit(0); //不会清洗缓冲区  不会打印hello
} 

2、研究退出状态。
   例题3: 让父进程去监听子进程的退出状态,如果子进程正常退出,则输出child exit success!  如果是异常退出,则输出child exit error!

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

int main(int argc,char *argv[])
{
    pid_t x;
    int state;
    x = fork();
    if(x > 0)
    {
        wait(&state);  //将来孩子退出时,除了会回收子进程的资源之外,还会将子进程的退出值放在state变量。
        if(state == 0)
        {
            printf("child exit success!\n");
        }
        else{
            printf("child exit error!\n");
        }    
    }
    
    if(x == 0)
    {
        sleep(3);
        printf("child exit!\n");
        exit(-1);  //异常退出
    }
    
    return 0;
}

3、在程序中,调用exit()函数与执行return语句有什么区别?

例子1: 

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    return 0;   //在main函数中执行return 0其实就是代表进程的退出
}  //输出helloworld

例子2: 
 

int main(int argc,char *argv[])
{
    printf("helloworld!\n");
    exit(0);    //进程的退出
}  //输出helloworld

例子3:

int func()
{
    return 0;  //如果不在main函数,return语句只是代表func函数的返回
}

int main(int argc,char *argv[])
{
    func();
    printf("helloworld!\n");
    return 0;
} //输出helloworld


例子4:

int func()
{
    exit(0);  //进程的退出
}

int main(int argc,char *argv[])
{
    func();
    printf("helloworld!\n");
    return 0;
}  //不输出任何内容


总结:
1)在main函数中,exit()与return语句是一样的   --> 因为main函数的返回其实就是等价于进程的退出
2)不在main函数中,exit()就代表进程的退出,return语句只是代表函数的返回。


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值