Linux 下进程与线程的基本概念

2019-10-01

关键字:进程、线程、信号量、互斥锁



什么是程序?

程序就是存放在磁盘上的指令和数据的有序集合,就是源代码编译产物。 它是静态的。

什么是进程?

进程就是操作系统为执行某个程序所分配的资源的总称。进程是程序的一次执行过程,因此它与程序不同,它是动态的。它的生命周期包括创建、调度、执行和消亡。

进程的内容主要包括以下三个部分:

1、正文段;

2、用户数据段;

3、系统数据段。

其中正文段与用户数据段两部分是从程序当中来的。而系统数据段则是操作系统分配的用来管理这个进程用的。

系统数据段中主要包含三个部分:

1、进程控制块;

process control block。用于存放和进程相关的属性,主要包括以下四个部分:

1、进程标识ID,简称 PID;

2、进程用户;

3、进程状态、优先级;

4、文件描述符集;

2、CPU寄存器值;

3、堆栈。

进程的类型有以下三种:

1、交互进程;

在 shell 下启动,运行在前台或后台。

2、批处理进程;

和终端无关,被提交到一个作业队列中以便顺序执行。

3、守护进程;

和终端无关,一直在后台运行,因而生命周期很长。直到操作系统关闭才会结束。

进程的状态有以下四种:

1、运行态;

进程正在运行或准备运行;

2、等待态;

进程正在等待某一个事件或某种系统资源,当条件满足时才被唤醒执行;等待态又可分为可中断型不可中断型两种。

3、停止态;

进程被中止,收到信号后可继续运行。

4、死亡态;

进程已经完全结束,但pcb没有被释放。又称为僵尸态。

查看进程信息可以通过以下几种方式:

1、ps;

用于查看系统进程当前的快照。

ps -ef   -->  查看当前所有进程的信息。

ps aux  -->  可以看到进程的当前状态。

ps aux -L  -->  可以看到进程的线程信息。

更多关于 ps 的参数及数值的含义可以通过 man ps 来寻求帮助。

2、top;

查看进程当前的动态信息。

一般用于查看进程占用资源的情况。

3、/proc;

这是一个目录,这个目录下存放着系统当前进程的所有信息。

通过 ps 命令查到每个进程的进程号,然后 /proc 目录下就会有和进程号相同的目录,这个目录下存放着的就是和这个进程相关的所有信息。

其它与进程相关的命令:

1、nice;

按指定的优先级运行进程。

进程的优先级范围为 -20 ~ 19,数字越小优先级越高,默认优先级为0。

例如:以既定的优先级启动程序:nice -n 9 ./test   表示 test 进程将以优先级9来启动。

普通用户只能设定 0~19 的优先级,只有管理员用户才能设置负数优先级。

2、renice;

改变正在运行的进程的优先级。

例如:renice -n 7 29070   后面跟的是要修改的 PID。

3、jobs;

查看当前的后台进程。

4、fg;

将后台作业变为前台运行。

例如:fg 2   将作业号为2的后台进程变成前台进程。

5、bg;

将处于挂起状态的后台进程运行起来。

例如 bg 2,将作业号为2的后台挂起进程在后台运行起来。

6、如何将一个进程在后台运行?

主要有两种方式。

一种是在运行程序之前以 ‘&‘ 符号结尾。以该符号结尾的进程在执行以后将直接在后台运行而不会阻塞命令行。

另一种方式则是进程在前台运行时按下组合键 ‘ctrl + z‘ 。此时该前台进程将转为后台进程,但当前是处于停止态的,若想恢复进程的运行,则需要执行上述第 5 条的 bg 命令来唤醒进程。

何如创建进程?

#include <unistd.h>

pid_t fork(void);

创建失败时返回值 -1,创建成功时父进程返回子进程的进程号,子进程返回值 0。

子进程继承了父进程的几乎所有内容,但二者拥有独立的空间,运行时互不干扰。

当父进程结束后,子进程即成为孤儿进程,会由 init 进程收养。同时子进程会变为后台进程。

当子进程先于父进程结束时,父进程若没有及时回收,则子进程会变成僵尸进程。

子进程被创建以后会从 fork 函数的下一条语句开始执行。

如何结束一个进程?

#include <stdlib.h>

#include <unistd.h>

void exit(int status);

void _exit(int status);

exit() 函数结束进程前会刷新流缓冲区,而后面 _exit() 函数则不会。

关于 exec 函数族

进程在正常运行过程中可以通过调用 exec 函数族来执行另一个程序。

通过 exec 函数族执行了另一个程序后,进程的当前内容就会被替换,简单理解成从此该进程就属于另外一个被启动的程序了。因此,我们可以通过 exec 函数族来让父子进程执行不同的程序。

#include <unistd.h>

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

下面是一个例子:

if(execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL) < 0){
    perror("execl");
}

if(execlp("ls", "ls", "-a", "-l", "/etc", NULL) < 0){
    perror("execl");
}

这两个例子都表示要执行一条 shell 命令: ls -a -l /etc。 两个函数的最后一个参数都必须为 NULL。

两个函数的区别就是 execlp 函数会去 $PATH 中查找程序运行,而 execl 不会。因此 execlp 在执行 ls 命令时可以省略完整路径名称。

#include <unistd.h>

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

与上面 execl 就最后一个字母不同。 l 其实代表 list,表示传的参数通过一个可变参数形式传入。这里的 v 则表示 vector,是数组之意。

不过在使用方式上就大同小异了。

char *arg[] = {"ls", "-a", "-l", "/etc", NULL};

if(execv("/bin/ls", arg) < 0){
    perror("/bin/ls");
}

if(execvp("ls", arg) < 0){
    perror("/bin/ls");
}

在程序中执行 shell 命令的函数:

#include <stdlib.h>

int system(const char *command);

执行成功后会返回 command 命令的返回值,失败会返回 EOF。

执行该命令后当前进程会阻塞直到 command 命令执行完毕为止。

进程回收:

#include <unistd.h>

pid_t wait(int *status);

执行成功时返回被回收的子进程PID,失败时返回 EOF,并设置 errno。

若子进程没有结束父进程就去回收它,父进程就会阻塞直至回收成功。

若父进程创建了多个子进程,则哪个子进程先结束就先回收哪个。理论上来讲,要回收多少个子线程就得调用多少次 wait() 函数。

参数 status 用于保存子进程的返回值和结束方式的地址,若传入 NULL,表示直接释放 PCB,不接收返回值。

进程的返回值和结束方式:

一个进程可以通过三种方式来正常结束:

1、exit

2、_exit

3、return ( 0 ~ 255 )

父进程调用 wait(&status) 来接收子进程的结束结果。当 wait() 函数执行过去以后,可以通过几个系统宏来提取 status 中的信息。

1、WIFEXITED(status)

判断子进程是否正常结束。

2、WEXITSTATUS(status)

获取子进程返回值。

3、WIFSIGNALED(status)

判断子进程是否是被信号非正常结束的。

4、WTERMSIG(status)

提取结束子进程的信号类型。

另一种进程回收的方式:

#include <unistd.h>

pid_t waitpid(pid_t pid, int *status, int option);

对指定进程以指定的方式进行回收。这种回收子进程的方式要比 wait() 灵活一点。

执行成功时返回被回收的子进程PID或0,失败时返回 EOF。

pid 值是具体的子进程值,也可以是 -1,-1表示任意一个子进程。

option有两种值,0 或宏 WNOHANG。 0表示阻塞,另一是不阻塞。

守护进程

通常在操作系统启动时运行,要待操作系统关闭时才会退出。

在linux系统中有大量守护进程,很多服务程序都以守护进程的形式在运行。

守护进程的三大特点:

1、后台运行;

2、与终端无关; 无法通过终端输入输出。

3、周期性地执行某种任务或等待处理特定的事件。

关于会话与控制终端:

Linux 以会话、进程组的方式管理进程。

每个进程都属于一个进程组。

会话是一个或多个进程组的集合。通常用户打开一个终端时就会创建一个会话,所有通过该终端运行的进程都属于该会话。

终端关闭时,所有相关进程都会结束。守护进程与终端无关,才能在终端关闭时继续运行。

守护进程的创建:

守护进程的创建是需要依赖于交互进程的。

通常的做法是在交互进程中创建一个子进程,然后将父进程退出,此时子进程就变成孤儿进程,但仍继续运行在后台。第二步是在子进程中创建新的会话:setsid() 函数可以实现。此时这个子进程将会成为新会话的组长,并且脱离原先会话的管控,即此时已经与旧终端无关了,它同时也不和任何一个终端相关了。第三步是修改当前工作目录: chdir("/") 函数可以实现,参数是要切换的工作路径。 第四步是修改文件权限掩码:umask(0) 函数可以实现 即将掩码值设为0,不限制任何权限位,这个掩码的设置只对当前进程有效,只是局部的。第五步是要将父进程打开的文件描述符都关闭,通常使用如下的关闭方式:

for(i = 0; i < getdtablesize(); i++){
    close(i);
}

守护进程示例:

创建一个守护进程,每隔1秒将当前时间值写入 time.log 文件中。

int main(){
  pid_t pid;
  FILE *fp;
  time_t t;
  int i;

  if((pid = fork()) < 0){
    perror("")
  }  else if(pid > 0){
    exit(0);
  }

  setsid();
  umask(0);
  chdir("/tmp");
  for(int i = 0; i < getdtablesize(); i++){
    close(i);
  }
  fp = fopen("time.log", "a")
  while(1){
    time(&t);
    fprint(fp, "%s", ctime(&t));
    fflush(fp);
    sleep(1);
  }

}

线程

进程间的切换资源开销很大,因此就出现了线程。 线程还有一个别称:轻量级进程,英文缩写LWP。

同一个进程中的线程是共享相同的地址空间的。使用多线程开发能够大大提升任务切换的效率,说白了就是能让程序运行的更快。

linux 不区分进程、线程,这些东西在 linux 里都被视为任务。

线程是由进程创建并管理的,所以当进程结束时,其下的所有线程都会结束。

线程的创建:

#include <pthread.h>

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

函数执行成功时返回值 0,失败返回相应错误码。

thread 代表一个线程对象。

attr 表示一个结构体指针,它表示的是线程属性,NULL 代表默认属性。

第三个参数是一个函数指针。它有一个 void * 的参数,同时返回值也是 void *。这个函数指针就是你想让哪个函数作为子线程来执行。

第四个参数 arg 就是要传递给 routine 函数的参数。

结束线程:

#include <pthread.h>

void pthread_exit(void *retval);

结束当前线程。线程结束的时候该线程所创建的所有资源都会被释放。

线程的回收:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

执行成功返回值 0,失败返回相应错误码。

thread 就是要回收的线程的对象。

retval 就是用来接收线程返回值的指针的地址。

该函数是一个阻塞函数,调用后会一直阻塞直到线程结束返回。

线程间通信:

线程是共享同一进程的地址空间的,拟线程间的通信将会很容易,直接就可以通过全局变量来交换数据。但这种访问的便利性也带来了一些风险,通常当有多个线程访问相同的共享数据时需要同步互斥锁

同步机制:

同步指的是多个任务按照事先约定的顺序先后地完成一件事情。

同步机制里最常用的就是 信号量。信号量可以决定线程当前是继续运行还是等待。

信号量代表某一类资源,其值表示系统中该资源的数量。因此它是一个非负数的值。

信号量是一个受保护的变量,只能通过三种操作来访问:

1、初始化;

2、P操作(申请资源);

3、V操作(释放资源)。

信号量的PV操作:

当进行P操作时,它会去判断当前信号量的值是否大于0,若是,则申请P操作的任务继续运行,同时信号量的值减一。若否,则阻塞。V操作则是先让信号量的值加一,再判断当前是否有正在等待资源的任务以让它继续运行。

信号量的类别:

POSIX定义了两种信号量:

1、无名信号量

它是一种基于内存的信号量。通常都用于进程内部线程之间的通信。

2、有名信号量

既可以用于进程之间也可以用于线程之间。

pthread 库中对信号量常用的操作:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);  信号量的初始化。

int sem_wait(sem_t *sem); P操作

int sem_post(sem_t *sem); V操作

sem_init 函数:

执行成功返回0,失败返回EOF,并设置 errno。第一个参数指向要初始化的信号量对象第二个参数表示是进程间还是线程间,0表示线程间,1表示进程间。第三个参数则是信号量的初值了。

线程的互斥:

临界资源:它是同一时刻只允许一个任务访问的共享资源。

临界区:访问临界资源的代码。

互斥机制:mutex 互斥锁。互斥锁的特性就是同一时刻最多只能被一个任务所持有。它的状态要么是空闲状态,要么是被某个任务所持有。任务访问临界资源前先申请锁,访问完了以后释放锁。

互斥锁的初始化

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

执行成功时返回值0,失败时返回对应错误码。

mutex 即指向要初始化的互斥锁的指针。

attr 用以设置锁的属性,传 NULL 表示使用默认属性。

申请互斥锁

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

成功返回0,失败返回对应错误码。

mutex 就是你想去申请的锁。当这个锁处于空闲状态时,申请锁的任务即拿到了这个锁,任务可以继续执行,否则这个任务就只能阻塞起来了。

释放互斥锁

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

与上面的申请锁相对应。



Linux 下进程与线程的基本概念

原文地址:https://www.cnblogs.com/chorm590/p/20191001_.html

时间: 2024-10-10 22:19:29

Linux 下进程与线程的基本概念的相关文章

Linux下进程与线程的区别及查询方法

在平时工作中,经常会听到应用程序的进程和线程的概念,那么它们两个之间究竟有什么关系或不同呢?一.深入理解进程和线程的区别 1)两者概念 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是指进程内的一个执行单元,也是进程内的可调度实体. 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源,只拥有一点 在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线

理解Linux的进程,线程,PID,LWP,TID,TGID

在Linux的top和ps命令中,默认看到最多的是pid (process ID),也许你也能看到lwp (thread ID)和tgid (thread group ID for the thread group leader)等等,而在Linux库函数和系统调用里也许你注意到了pthread id和tid等等.还有更多的ID,比如pgrp (process group ID), sid (session ID for the session leader)和 tpgid (tty proce

linux查看进程的线程数

top -H -p $PID  #查看对应进程的那个线程占用CPU过高 1.top -H 手册中说:-H : Threads toggle 加上这个选项启动top,top一行显示一个线程.否则,它一行显示一个进程. 2.ps xH 手册中说:H Show threads as if they were processes 这样可以查看所有存在的线程. 3.ps -mp 手册中说:m Show threads after processes 这样可以查看一个进程起的线程数. 查看进程 top 命令

Linux中进程与线程的概念以及区别

linux进程与线程的区别,早已成为IT界经常讨论但热度不减的话题.无论你是初级程序员,还是资深专家,都应该考虑过这个问题,只是层次角度不同罢了.对于一般的程序员,搞清楚二者的概念并在工作中学会运用是其思考的主要问题:对于资深工程师,如何在系统层面实现两种技术及其各自的性能和实现代价是其思考的主要问题.由此可见进程和线程在IT界的重要地位. 进程和线程是操作系统的基本概念,它们之间既有区别又有联系.从定义来看,它们比较抽象,很难理解.今天给大家打个比方,教大家用类比的方法去理解它,掌握它.其实,

Linux下进程间Socket通信调试debug方法

在一个复杂的软件系统中,往往需要有各个组件之间的数据传递,在组件间数据传递过程中,又会不可避免的出现一些小问题,这时候我们就需要来进行debug了,由于最近的一个系统使用到了collectd和rrdcached来收集数据和画图,它们之间采用了Unix socket通信,因此小小的学习了一下相关知识. 首先我们来回忆下Linux下进程通信有哪些方法: 管道(Pipe)及有名管道(FIFO)\UNIX BSD 信号(Signal) \UNIX BSD 报文消息队列(Message)\UNIX sys

【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通信,在一个进程之中,只能单一的对其写或者是读,而不可以及执行写操作又执行读操作.这一点,我们可以将其想象成我们的水管,分别连着不同的两端,在有水流的时候,一端只能进行输入,另一端只能进行输出,而不可以同时输入和输出. 管道又分为有名管道和匿名管道,两者的区别在于对于匿名管道,其只能在具有亲缘关系的父

Linux下进程管理工具之(二):htop

实验环境: CentOS release 6.6(Final)  一台 IP地址:172.16.249.230 Htop是一款运行于Linux系统监控与进程管理软件,用于取代Unix下传统的top.与top只提供最消耗资源的进程列表不同,htop提供所有进程的列表,并且使用彩色标识出处理器.swap和内存状态. 用户一般可以在top无法提供详尽系统信息的情况下选择安装并使用htop.比如,在查找应用程序的内存泄漏问题时.与top相比,htop提供更方便.光标控制的界面来杀死进程. htop用C语

LINUX下的简单线程池

前言 任何一种设计方式的引入都会带来额外的开支,是否使用,取决于能带来多大的好处和能带来多大的坏处,好处与坏处包括程序的性能.代码的可读性.代码的可维护性.程序的开发效率等. 线程池适用场合:任务比较多,需要拉起大量线程来处理:任务的处理时间相对比较短,按照线程的周期T1(创建阶段).T2(执行阶段).T3(销毁阶段)来算,执行阶段仅占用较少时间. 简单的线程池通常有以下功能:预创建一定数量的线程:管理线程任务,当工作线程没有事情可做时休眠自己:销毁线程池. 复杂一些的线程池有额外的调节功能:管

Linux下进程线程,Nignx与php-fpm的进程线程方式

1.进程与线程区别 进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源. "进程——资源分配的最小单位,线程——程序执行的最小单位" 进程有独立的地