进程与线程基础----(理论)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_42948022/article/details/102510896

进程与线程

(1)有一段程序供其执行。
(2)有进程专用的系统堆栈空间。
(3)在内核有task_struct数据结构。
(4)有独立的存储空间,拥有专有的用户空间。**********

如果只具备前面三条而缺第四条,那就称为“线程”。
如果完全没有用户空间,就称为“内核线程”;
而如果共享用户空间则就称为“用户线程”。

Linux系统运行时,第一个进程是在初始化阶段“捏造”出来的(init_task)。
而此后的进程或线程都是由一个已经存在的进程像细胞分裂那样通过系统调用复制出来的。
成为“fork”(分叉)或“clone”(克隆)。

进程虚拟地址的结构

https://www.cnblogs.com/Joezzz/p/9803344.html

进程控制块结构体:task_struct

定义在include/linux/sched.h
Linux在内存空间中开辟了一个专门区域存放所有进程的进程控制块
系统初始化后,建立第一个task_struct数据结构是:INIT_TASK。
当建立新进程的时候,Linux为新的进程分配一个task_struct结构。
task_struct解析:https://blog.csdn.net/liuwenjuan_cherry/article/details/80807436

进程状态信息(state, flags, ptrace)
调度信息(static_prio, normal_proi, run_list, array, policy)
内存管理(mm, active_mm)
进程状态位信息(binfmt, exit_state, exit_code, exit_signal)
身份信息(pid, tgid, uid, suid, fsuid, gid, egid, sgid, fsgid)
家族信息(real_parent, parent, children, sibling)
进程时间信息(realtime, utime, stime, starttime)
时钟信息(it_prof_expires, it_virt_expires, it_sched_expires)
文件系统信息(link_count, fs, files)
IPC信息(sysvsem, signal, sighand, blocked, sigmask, pending)

linux开机进程树:

在Linux中每个进程都由其父进程创建的。Linux系统启动时将创建init进程,其PID为1是系统的第一个进程,其将进行一些初始化工作;
init进程创建getty打开终端设备,读取用户名等;
getty进程创建login进程,用户在终端登录成功后创建shell进程,之后又用户通过shell运行命令相关进程来操作系统;
系统中进程要么由init创建,要么由init所启动的进程创建,从而形成一棵以init为祖先的树型结构。

init进程
getty进程
login进程
shell进程
用户进程

pid_t pid

int 0~32767
用户可分配pid最大值存放在:/proc/sys/kernel/pid_max下,可以改这个值。

zy@zy-virtual-machine:/proc/sys/kernel$ cat pid_max
131072

子进程对父进程资源的处理

PCB:子进程获得父进程的副本,并修改部分属性。
代码段:共享父进程的代码段,因为代码段只读属性。
数据段、堆、栈:读时共享,写时复制技术。
	一般进程大部分资源在虚拟内存中,
	为节省全部复制浪费的时间空间,
	创建子进程时这些区域不复制。
	只有在对这些区域进行修改时才复制部分内存页

孤儿进程

定义:父结束,子未结束,
将被其他进程领养。

int main()
{
	pid_tpid;
	if((pid=fork())==-1)
		perror("fork");
	else if(pid==0)		\\子进程
	{
		printf("pid=%d,ppid=%d\n",getpid(),getppid());
		sleep(2);
		printf("pid=%d,ppid=%d\n",getpid(),getppid());}
	else		\\父进程立刻中止
	{
		exit(0);  
	}
}

僵死进程

定义:子进程结束,父进程还没有善后。(子进程的进程ID、终止状态等信息还在内存中)
什么时候释放资源:1:父进程处理,2:父进程结束,被init领养才能释放资源。

int main()
{
	pid_tpid;
	if((pid=fork())==-1)
		perror("fork");
	else if(pid==0)
	{
		printf("child_pidpid=%d\n",getpid());
		exit(0);  \\子进程终止
	}
	else{
		sleep(3);
		system("ps");
		exit(0);  \\父进程终止
	}
}

如何一次处理多个僵尸子进程

多条客户端连接如果同一时间并行退出,导致服务器端多个子进程同一时间全部退出。
而SIGCHLD是不可靠信号,同时来多条信号可能无法处理,导致出现僵尸进程。
如果使用while循环wait又会阻塞父进程。
所以采取waitpid()函数来解决这个问题。

进程用到的函数:

pid_t getpid(void)	//返回当前PID
pid_t getppid(void)	//返回当前父进程PID

pid_t fork(void);  //父子进程同时执行
//父进程 >0    子进程 ==0   错误返回-1给父进程
pid_t vfork(void);	//子进程优先于父进程执行

exit()
_exit()

pid_t wait(int*status);
pid_t waitpid(pid_t pid, int *status,int options);
//pid>  0 : 等待进程id为pid的子进程
//pid== 0 : 等待与调用进程同组的任一子进程
//pid== -1: 等待任一子进程wait()
//pid<  -1: 等待进程组号为|pid|的任一子进程
//status:子进程退出状态,不需要可置为NULL
//options:可为0,若为WNOHANG,则若指定子进程未结束立即返回0。
//返回值:成功返回终止子进程id,返回0表示没有子进程,错误返回-1置errno

WIFEXITED(status);
//若为正常终止,返回真,并继续调用WEXITSTATUS(status)宏获取退出状态的低8位。
WIFSIGNALED(status);
//若为异常终止,返回真,并继续调用WTERMSIG(status)宏获取导致子进程终止的信号编号。

#include <unistd.h>
intexecl(constchar *path, constchar *arg0, ...);
intexeclp(constchar *file,  constchar *arg0, ...);
intexecle(constchar *path, constchar *arg0,,constchar *envp[ ]);
intexecv(constchar *path, constchar *argv[ ]);
intexecvp(constchar *file,  constchar *argv[ ]);
intexecve(constchar *path, constchar *argv[ ], constchar *envp[ ];
path/file:可执行文件名
arg/argv:传给path/file可执行文件的命令行参数
envp:环境变量

path和非p的区别:可执行文件名是否需要指定路径
list、vector的区别:命令行参数形式不同
env和非e的区别:是否需要指定环境变量列表
只有execve是系统调用

int system(constchar *cmdstring);
原理:创建子进程、子进程执行shell程序执行命令、
父进程等待子进程结束。
如果fork失败或waitpid返回除EINTR之外的错误,返回-1,置errno
如果exec失败,返回127
三个函数都成功,返回值是shell的终止状态。

fork/vfork  	子进程拷贝父进程代码段、数据段等
execve 			新程序取代子进程的代码段、数据段等

pid_t getpgid(void);
int setpgid(pid_t pid, pid_t pgid);
tsetpgid作用:加入一个现有进程组或创建一个新进程组

int setsid();		//创建会话
当进程是会话的领头进程时,setsid()调用失败并返回(-1,调用成功后,返回新的会话的ID;
创建会话的新进程也是会话中一个进程组长,也是会话首进程;
会话ID、进程组ID都与这个进程ID相同;
一个会话中可有多个后台进程组,最多只有一个前台进程组;
终端产生的信号(ctrl+c、ctrl+z)由前台进程组接收;

猜你喜欢

转载自blog.csdn.net/qq_42948022/article/details/102510896