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函数时发生了什么?
-
fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一摸一样。子进程和父进程使用了相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它会复制父进程的一切数据,然后各自运行,相互之间没有影响。
-
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);
}
// 程序直接退出,析构函数会释放资源。
}
运行结果一(有注释):
运行结果二(无注释):