Linux系统笔记:线程与进程

进程与线程的概念

1,进程:

(1)正在运行的一个程序
(2)它代表一种资源的载体(独立的应用的应用程序)
(3)资源管理的最小单位
(4)每一个进程独立包括虚拟内存,文件描述符资源,信号资源等,不与其他进程共享资源
应用场景:
1,调度第三方程序
2,调度其他程序的时候,我们需要传输一定的资源或者是指令过去给另外一个程序时,我们需要应用进程间通信
3,启用服务

2,线程:

(1)系统调度的最小单位(CPU在轮询指令运行的最小单位)
(2)进程下面的一个子级单位(所有的线程都是在进程的基础上运行的),一个进程当中,可以运行多个线程
(3)所有的线程共享进程的所有资源
(4)每一个线程独立一片栈空间(栈空间默认大小为8M)
应用场景:
基本上所有的多任务的开发,优先采用多线程

查看线程命令:

  • ps -Lf 查看进程id +线程号。

3、两者的差别与共同点:

  • 进程:有独立的 进程地址空间。有独立的pcb。 分配资源的最小单位。

  • 线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。

线程独享与共享:

  • 独享 栈空间(内核栈、用户栈)

  • 共享 ./text./data ./rodataa ./bsss heap —> 共享【全局变量】(errno)

进程

1,进程的创建:

进程的创建过程我们可以理解成为“分身”的过程
fork:

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

函数功能:
创建出一条子进程
函数在执行的过程当中,会将父进程的资源复制一份,放到子进程里面去运行,其中下面是会被子进程继承的资源
1,父进程的运行的用户的ID跟组ID
2,环境变量(库路径,命令路径,命令路径等等)
3,进程组ID跟会话ID
4,打开的文件描述符
5,信号响应函数
6,虚拟内存(堆,栈,程序段落等等)
以下属性就是独立,没有继承的:
1,进程ID
2,记录锁(文件锁)
3,挂起的信号
返回值:
成功创建子进程的情况下会将0返回给子进程,子进程的PID返回父进程,如果失败返回-1,子进程不会被创建

2、回收子进程

等待函数wait:

pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

函数功能:
回收子进程

pid:>0:等待子进程为pid退出(不是一个进程组也没关系)
pid:0:等待该调用者进程的任意一个子进程(如果不是一个进程组的子进程不接受)
pid:-1:等待任意的一个子进程(就算不是一个进程组的子进程也接收)
pid:<-1:等待pid这个数值的绝对值所对应的进程组ID里面的子进程
参数:

  • wstatus:用来存放子进程当中的返回状态值(exit里面的返回值就会放在这里面),可以通过多个函数来分析这个值:
    在这里插入图片描述

  • options:0则代表正常阻塞等待子进程退出
    在这里插入图片描述

返回值: 成功则返回接收的子进程的ID值,失败则返回-1

获取进程ID系列函数:
getpid:获取当前的程序的进程ID
getppid:获取当前程序的父进程ID
getpgid(pid):获取pid这个进程的组id
setpgrp:设置进程组ID为自己进程的ID

3、进程退出系列函数:

exit:
#include <stdlib.h>
void exit(int status);

参数:
status:会将这个值&0377后传输给等待回收这个进程的人(也就是调用wait系列函数的人)
退出本调用这个函数的进程
退出之前先执行atexit或者是on_exit函数所注册过的退出处理函数群,然后再清楚所有标准IO缓冲区,再退出进程

_exit:

#include <unistd.h>
void _exit(int status);

参数:
status:会将这个值&0377后传输给等待回收这个进程的人(也就是调用wait系列函数的人)
直接退出调用这个函数的进程
直接退出,中间不经过任何操作

atexit:

#include <stdlib.h>
int atexit(void (*function)(void));

简化版本的退出处理函数,在程序调用exit函数或者是main函数return的时候去执行这个函数所注册过的退出处理函数。
参数:
function:这是一个 void (*)(void)函数指针,要求传入函数类型格式,并且将函数名放在这里即可
细节:
按照后入先出的原则执行注册的退出处理函数,可以注册多个

on_exit:

#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);

复杂版本的退出处理函数,在程序调用exit函数或者是main函数return的时候去执行这个函数所注册过的退出处理函数。
参数:
function:这是一个 void (*)(int,void *)函数指针,要求传入函数类型格式,并且将函数名放在这里即可,其中int的这个参数放的是exit或者是main函数return的数值(status值),void *则是on_exit第二个参数。
arg:传输给function的第二个参数
细节:
按照后入先出的原则执行注册的退出处理函数,可以注册多个

4、exec系列函数的用法

exec系列函数:

int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

都是用来调度第三方程序:
execl: 调用一个指定路径下的应用程序,传输给指定应用程序的参数通过后面的一个一个字符串传输

execlp: 调用一个命令,传输给命令的参数通过后面的一个一个字符串传输

execv: 调用一个指定路径下的应用程序,传输给指定应用程序的参数通过字符串数组传输

execvp: 调用一个命令,传输给命令的参数通过字符串数组传输
一经过上面的函数调用,本进程会丢失原本的资源(虚拟内存资源等等),会保留类似于进程ID,文件资源(除非一开始打开的时候声明调用exec系列函数的时候自动关闭(open函数可以声明O_CLOEXEC))重新加载新的程序的资源
注意,上面的参数中,如果是execl,跟execlp必须用NULL指针作为最后的参数;如果是execv,execvp里面的argv这个字符串数组,最后一个元素的值也必须是NULL指针。
参数:
path:具体需要调用的程序的位置及名字
arg:传入给应用程序的第一个参数,一般为程序本省的名字
…:需要继续传入应用程序的参数,可以传入多个参数
file:需要调用的命令名字
argv:多个传入参数所形成的一个字符串数组,而且最后一个元素必须以NULL指针作为结尾
返回值:
成功则没有返回值,因为调用这个函数的原本的程序会被覆盖掉,失败则返回-1,errno会被设置。

vfork:
专门为了exec系列函数服务的一个创建进程的函数,功能跟参数与fork是一模一样,只有一下操作是不一样的:
1,vfork成功创建子进程,子进程跑起来,引用的内存是父进程的内存
2,vfork成功创建子进程之后,父进程陷入睡眠
3,当vfork创建出来的子进程调用了exec系列函数,去加载第三方程序的时候或者是子进程结束,父进程才会被唤醒
vfork这样的操作是为了节约fork函数创建子进程而进行的内存拷贝这一环节的时间(vfork子进程是不会拷贝父进程的虚拟内存的内容),

5、守护进程/精灵进程(服务)

1,在后台默默的运行
2,不依赖于所谓的命令终端
3,本身没有什么外界的限制

实现的过程:

/*1,先忽略终端的挂断信号对这个程序的影响*/
	signal(SIGHUP, SIG_IGN);

	/*2,新建一个子进程,让父进程退出,这个时候子进程就不会接收到控制终端的信号跟内容*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*3,新建一个会话,脱离开原本的会话(也就是这个控制终端)*/
	setsid();

	/*下面的动作都是为了让我们的程序更加的纯净*/

	/*4,新建多一个子进程,脱离开会话管理的权限*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*5,再让这个子进程脱离开原本的进程组*/
	setpgrp();


	/*6,关闭掉原本的所有文件描述符*/
	max_fd = sysconf(_SC_OPEN_MAX);
	for(i=0; i<max_fd; i++)
		close(i);

	/*7,改变工作路径到根目录*/
	chdir("/");

	/*8,改变原有的掩码,成为没有任何权限影响的0*/
	umask(0);

系统日志:
openlog函数打开日志,syslog写入日志,closelog关闭日志。

#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);

ident:打开日志文件的时候,跟日志文件说你是谁

openlog函数发起到系统日志服务器的连接,参数ident是要向每个消息加入的字符串,典型的情况是要设置成程序的名称。
参数option是下面一个或多个值的“或”
LOG_CONS 如果系统日志服务器不能用,写入控制台
LOG_NDELAY 立即打开连接,正常情况下,直到发送第一条消息才打开连接
LOG_PERROR 打印输出到stderr
LOG_PID 每条消息中包含进程 PID

参数facitity指定程序发送消息的类型。
LOG_AUTHPRIV 安全授权消息
LOG_CRON 时钟守护进程:cron和at
LOG_DAEMON 其他系统守护进程
LOG_KERN 内核消息
LOG_LPR 打印机子系统
LOG_MAIL 邮件子系统
LOG_USER 默认

参数priority指定消息的重要性。
LOG_EMERG 系统不能使用
LOG_ALERT 立即采取措施
LOG_CRIT 紧急事件

LOG_ERR 出错条件
LOG_WARNING 警告条件
LOG_NOTICE 正常但重大事件
LOG_INFO 信息消息
LOG_DEBUG 调试信息

syslog代码例子:
yslog(LOG_INFO, “my daemin is OK”);

严格的说,openlog和closelog是可选的,因为函数syslog在首次使用的时候自动打开日志文件。
linux系统上日志文件通常是/var/log/syslog。

线程

1、创建线程

函数原型 :

 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); 

函数功能: 创建一条新线程
头文件: #include <pthread.h>

参数:

  • thread: 新线程的 TID(线程号,传出参数)
  • attr: 线程属性
  • start_routine: 线程例程
  • arg: 线程例程的参数
  • 返回值 :成功 0 失败 errno

猜你喜欢

转载自blog.csdn.net/mbs520/article/details/107431651
今日推荐