Linux进程(有代码,有图,有知识整理)

本文介绍了Linux系统中的进程基础,包括进程的概念、查看进程的`ps`命令及`getpid`函数的使用。接着讲解了多进程的创建,通过`fork`函数示例展示了父进程与子进程的执行流程。最后讨论了僵尸进程的产生及其避免方法,指出忽略`SIGCHLD`信号可以防止僵尸进程的产生。
摘要由CSDN通过智能技术生成

Linux系列文章目录

第一章、信号

第二章、进程



一、 进程的基础知识

1. 进程的基本概念

进程就是正在内存中运行的程序,Linux下的一个进程的内存有三个部分的数据

代码段:存放程序代码

堆栈段:存放程序返回地址、程序的参数以及程序的局部变量

数据段:存放程序的全局变量,常数以及动态数据分配的数据空间

系统如果同时运行多个相同的程序,它们的“代码段”是相同的,“堆栈段”和“数据段”是不同的。

2. ps查看进程

king@ubuntu:~$ ps
   PID TTY          TIME CMD
  4280 pts/4    00:00:00 bash
  4294 pts/4    00:00:00 ps
//查看系统全部进程
king@ubuntu:~$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  1 21:53 ?        00:00:36 /sbin/init noprompt
root          2      0  0 21:53 ?        00:00:00 [kthreadd]
root          3      2  0 21:53 ?        00:00:00 [ksoftirqd/0]
root          5      2  0 21:53 ?        00:00:00 [kworker/0:0H]
//查询进程
king@ubuntu:~$ ps -ef | grep mysql
king       4299   4280  0 22:54 pts/4    00:00:00 grep --color=auto mysql

UID:启动进程的操作系统用户

PID:进程编号

PPID:进程的父进程的编号

C:CPU使用的资源百分比

STIME:进程的启动时间

TTY:进程所属的终端

TIME:使用掉的CPU时间

CMD:执行的是什么指令

3. getpid函数获取进程编号

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

int main() {

    printf("本程序的进程编号是:%d\n", getpid());
    
    getchar();

    return 0;
}
king@ubuntu:~/share/Student/Process$ gcc -o getpid getpid.c 
king@ubuntu:~/share/Student/Process$ ./getpid
本程序的进程编号是:4375
king@ubuntu:~$ ps -ef | grep getpid
king       4375   4280  0 23:06 pts/4    00:00:00 ./getpid
king       4393   4335  0 23:09 pts/5    00:00:00 grep --color=auto getpid
king@ubuntu:~$ 

        1. 进程的编号是系统动态分配的,相同的程序在不同的时间执行进程的编号是不同的。

        2. 进程的编号会循环使用,但是,在同一时间,进程的编号是唯一的,也就是说,不管任何时间,系统不可能存在两个编号相同的进程。

二、多进程的基础知识

pid_t fork()	// 返回进程编号

fork函数用于产生一个新的进程,函数返回值pid_t是一个整数,在父进程中,返回值是子进程编号,在子进程中,返回值是0。

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

int main() {
    printf("本程序的进程编号是:%d\n", getpid());

    int ipid = fork();
    sleep(1);        // sleep等待进程的生成
    printf("pid=%d\n", ipid);

    if (ipid != 0) printf("父进程编号是:%d\n", getpid());
    else printf("子进程编号是:%d\n", getpid());

    sleep(30);      
    return 0;
}
king@ubuntu:~/share/Student/Process$ gcc -o fork fork.c 
king@ubuntu:~/share/Student/Process$ ./fork
本程序的进程编号是:4444
pid=0
pid=4445
父进程编号是:4444
子进程编号是:4445
king@ubuntu:~$ ps -ef | grep fork
message+    732      1  0 21:54 ?        00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
king       4444   4280  0 23:31 pts/4    00:00:00 ./fork
king       4445   4444  0 23:31 pts/4    00:00:00 ./fork
king       4447   4335  0 23:31 pts/5    00:00:00 grep --color=auto fork

一个函数(fork)返回了两个值?if和else被同时执行了?调用fork函数时发生了什么?

  1. fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一摸一样。子进程和父进程使用了相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它会复制父进程的一切数据,然后各自运行,相互之间没有影响

  2. fork函数对返回值做了特别的处理,调用fork函数之后,在之程序中fork的返回值是0,在父进程中fork的返回值仍然是远进程的编号,程序员可以通过fork的返回值在同一份代码里区分父进程和子进程,然后执行不同的代码。

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

void fatherfunc() {
    printf("父进程\n");
}

void childfunc() {
    printf("子进程\n");
}

int main() {
    if (fork() > 0) fatherfunc();
    else    childfunc();

    printf("父进程和子进程运行完自己的函数后都会到这里\n");
    sleep(30);      
    return 0;
}
king@ubuntu:~/share/Student/Process$ gcc -o fork1 fork1.c 
king@ubuntu:~/share/Student/Process$ ./fork1
父进程
父进程和子进程运行完自己的函数后都会到这里
子进程
父进程和子进程运行完自己的函数后都会到这里

三、搭建多进程并发的网络通信服务端框架

客户端代码:

#include "../_freecplus.h"


int main(int argc,char *argv[])
{
    CTcpClient TcpClient;   // 创建客户端的对象。
    
    if (TcpClient.ConnectToServer("192.168.126.139",5858)==false) // 向服务端发起连接请求。
    {
        printf("TcpClient.ConnectToServer(\"192.168.126.139\",5858) failed.\n"); return -1;
    }

    char strbuffer[1024];    // 存放数据的缓冲区。

    for (int ii=0;ii<10;ii++)   // 利用循环,与服务端进行5次交互。
    {
        memset(strbuffer,0,sizeof(strbuffer));
        snprintf(strbuffer,50,"This is %d super woman, number = %03d",ii+1,ii+1);
        printf("pid: %d send: %s\n", getpid(), strbuffer);
        if (TcpClient.Write(strbuffer)==false) break;    // 向服务端发送请求报文。

        memset(strbuffer,0,sizeof(strbuffer));
        if (TcpClient.Read(strbuffer,20)==false) break;  // 接收服务端的回应报文。
        printf("pid: %d recv: %s\n", getpid(), strbuffer);

        sleep(1);
    }

    // 程序直接退出,析构函数会释放资源。
}

服务端代码:

#include "../_freecplus.h"


int main(int argc,char *argv[])
{
    CTcpServer TcpServer;   // 创建服务端对象。
    
    if (TcpServer.InitServer(5858)==false) // 初始化TcpServer的通信端口。
    {
        printf("TcpServer.InitServer(5858) failed.\n"); return -1;
    }
    while (true) 
    {
        if (TcpServer.Accept()==false)   // 等待客户端连接。
        {
          printf("TcpServer.Accept() failed.\n"); return -1;
        }
        if (fork() > 0) continue;   // 让父进程等待客户端连接,子进程接发数据
        
        printf("client(%s)conntion\n",TcpServer.GetIP());

        char strbuffer[1024];  // 存放数据的缓冲区。

        while (true)
        {  
            memset(strbuffer,0,sizeof(strbuffer));
            if (TcpServer.Read(strbuffer,300)==false) break; // 接收客户端发过来的请求报文。
            printf("pid: %d recv: %s\n", getpid(), strbuffer);

            strcat(strbuffer,"ok");      // 在客户端的报文后加上"ok"。
            printf("pid: %d send: %s\n", getpid(), strbuffer);
            if (TcpServer.Write(strbuffer)==false) break;     // 向客户端回应报文。
        }

        printf("client down\n");    // 程序直接退出,析构函数会释放资源。

        exit(0);
    }
}

运行结果:

四、解决僵尸进程的问题

1. 什么是僵尸进程

        僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源。

2. 产生僵尸进程的原因

        一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)

3. 如何避免僵尸进程产生

在产生僵尸进程的代码段里加入代码:

signal(SIGCHLD, SIG_IGN);   // 忽略子进程退出的信号,避免产生僵尸进程

服务端代码:

#include "../_freecplus.h"


int main(int argc,char *argv[])
{
    //signal(SIGCHLD, SIG_IGN);   // 忽略子进程退出的信号,避免产生僵尸进程-----------------运行结果二的代码--------------

    CTcpServer TcpServer;   // 创建服务端对象。
    
    if (TcpServer.InitServer(5858)==false) // 初始化TcpServer的通信端口。
    {
        printf("TcpServer.InitServer(5858) failed.\n"); return -1;
    }
    while (true) 
    {
        if (TcpServer.Accept()==false)   // 等待客户端连接。
        {
          printf("TcpServer.Accept() failed.\n"); return -1;
        }
        if (fork() > 0) continue;   // 让父进程等待客户端连接,子进程接发数据
        
        printf("client(%s)conntion\n",TcpServer.GetIP());

        char strbuffer[1024];  // 存放数据的缓冲区。

        while (true)
        {  
            memset(strbuffer,0,sizeof(strbuffer));
            if (TcpServer.Read(strbuffer,300)==false) break; // 接收客户端发过来的请求报文。
            printf("pid: %d recv: %s\n", getpid(), strbuffer);

            strcat(strbuffer,"ok");      // 在客户端的报文后加上"ok"。
            printf("pid: %d send: %s\n", getpid(), strbuffer);
            if (TcpServer.Write(strbuffer)==false) break;     // 向客户端回应报文。
        }

        printf("client down\n");    // 程序直接退出,析构函数会释放资源。

        exit(0);
    }
}

客户端代码:

#include "../_freecplus.h"


int main(int argc,char *argv[])
{
    CTcpClient TcpClient;   // 创建客户端的对象。
    
    if (TcpClient.ConnectToServer("192.168.126.139",5858)==false) // 向服务端发起连接请求。
    {
        printf("TcpClient.ConnectToServer(\"192.168.126.139\",5858) failed.\n"); return -1;
    }

    char strbuffer[1024];    // 存放数据的缓冲区。

    for (int ii=0;ii<10;ii++)   // 利用循环,与服务端进行5次交互。
    {
        memset(strbuffer,0,sizeof(strbuffer));
        snprintf(strbuffer,50,"This is %d super woman, number = %03d",ii+1,ii+1);
        printf("pid: %d send: %s\n", getpid(), strbuffer);
        if (TcpClient.Write(strbuffer)==false) break;    // 向服务端发送请求报文。

        memset(strbuffer,0,sizeof(strbuffer));
        if (TcpClient.Read(strbuffer,20)==false) break;  // 接收服务端的回应报文。
        printf("pid: %d recv: %s\n", getpid(), strbuffer);

        sleep(1);
    }

    // 程序直接退出,析构函数会释放资源。
}

运行结果一(有注释):

运行结果二(无注释):


 推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值