进程与线程(一)

进程和线程都是现代操作系统中程序运行的基本单位,进程是一个具有独立功能的程序关于某个数据集合上的依次并发执行的运行活动,从进程演化出线程的主要目的就是为了更好地支持多处理器系统,提高性能;

在单线程结构的进程中,进程及时内部独立的执行单元,也是独立竞争资源的基本单元;

在多线程结构的进程中,进程是系统进行资源分配和保护的基本单元,而线程是进程内的独立的执行单元,线程不能离开进程和独立存在;

线程包含独立的堆栈和处理器及寄存器状态,每个线程共享其所附属的进程的所有资源;

一、进程

Linux操作系统使用一个称为进程控制块的数据结构task_struct来表示一个进程。当一个进程被创建时,系统为该进程建立一个task_struct结构,当进程结束时,系统会回收进程的task_struct结构;

进程可以产生子进程, 每一个进程都有一个标志,即进程ID,可通过getpid()函数获得其进程ID,通过getppid()函数获得其父进程ID,还有一些不常用函数,getpgrp()获得进程组ID,getpgid(pid_t pid)获得制定pid进程所属组的ID,getuid()获得进程所有者的ID,geteuid()获得进程有效用户的ID以及getegid()获得进程有效组ID;

1、进程的创建

一般情况下是使用fork(), vfork()两个函数来创建新的进程的,函数原型如下:

pid_t  fork(void);

pid_t  vfork(void);

这两个函数调用一次返回两次,向父进程返回刚创建子进程的ID,向子进程返回0,调用失败时,父进程返回-1,没有子进程创建。

fork()函数与vfork函数的区别:

调用fork函数时,子进程与父进程使用同一个代码段,因为其程序相同,对于数据段和堆栈段,系统则复制一份给新进程,并采用写时复制(copy-on-write)的技术,即当数据发生变化时,系统将有区别的页从物理上分离,使得在空间上的开销达到最小;fork()创建的子进程与父进程的执行顺序是无法控制的,需通过一系列的机制进行控制;

调用vfotk函数时,vfork()函数创建的子进程与父进程共享地址空间,即子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改同样为父进程可见,但是vfork()函数创建子进程后,父进程会被阻塞,知道子进程执行execv()或者exit();

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

int main(void)
{
   int data = 0;
   pid_t pid;
   int choose = 0;
   while((choose = getchar()) != 'q')
   {
      switch(choose)
      {
      case '1':
          pid = fork();
          if(pid < 0)
             perror("Error!\n");
          if(pid == 0)
          {
             data++;
             exit(0);
          }
          wait(pid);
          if(pid > 0)
            printf("data is %d\n",data);
          break;
      case '2':
          pid = vfork();
          if(pid < 0)
             perror("Error\n");
          if(pid == 0)
          {
             data++;
             exit(0);
          }
          wait(pid);
          if(pid > 0)
          {
             printf("data is %d\n",data);
          }
          break;
       default:
          break;
      }
   }
}

运行结果:

2.关于exec()类函数

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

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

<3>int  execle(const char *path,const char *arg,...,char * const envp[])

<4>int execv(const char *file, char * const  arg[],...);

<5>int  execve(const char *filename,char * const argv[],char *const envp[] );

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

后缀为p时,可以利用DOS的PATH变量查找到可执行文件;

后缀为l时,接受以逗号分隔的参数列表,列表以NULL指针作为结束标识;例:execl("/homr/robot/test",“test”,a,b,c,NULL),一般第一个参数为可执行文件名,a,b,c均为参数;

后缀为v时,接收以NULL结尾的字符串数组的指针,例:char *argv[] = {"/bin/cat","etc/passed","etc/group",NULL};

后缀为e时,传递指定参数envp;

以execlp函数为例:

#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
char command[256];

void main()
{
   int rtn;
   while(1)
   {
      printf(">");
      fgets(command,256,stdin);
      command[strlen(command)-1] = 0;
      if(fork() == 0)
      {
          execlp(command,command,NULL);
          perror(command);
          exit(-1);
      }
      else
      {
          wait(&rtn);
          printf("child process return %d\n",rtn);
      }
   }
}

结果如下:

3、进程等待

与vfork()不同,fork()创建的子进程与父进程的执行顺序是无法控制的,如果想要控制,必须有父进程使用wait()或waitpid(),其中wait()返回任一终止状态的子进程,而waitpid()等待特定的子进程。

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *status);

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

对于wait():参数status用于保存被收集进程退出时的状态,它是一个指向int类型的指针,进程一旦调用wait(),将立即阻塞自己,由wait自动分析当前进程是否有某个子进程退出,如果找到一个已经僵死的子进程,wait()将收集這个子进程的信息,并彻底销毁后返回,若没有找到一个僵死的进程,将一直阻塞,即等待,如果不关心子进程的退出状态,可以设置为NULL;

对于waitpid():pid>0时,等待进程ID为pid的子进程,pid= -1时,等待任何一个子进程退出,此时与wait函数差不多,pid = 0时,等待同一个进程组的任何子进程,如果子进程加入了其他进程组,则waitpid()函数不会对他做任何理睬,pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值;optiona 可以进一步控制waitpid()函数的操作,参数值可以为0,也可以为WNOHANG(如由pid指定的子进程并不立即可用,则waitpid()函数不阻塞,返回0)和WUNTRACED(如pid指定的任一子进程已暂停,且未报告过,即没有获得其状态,则返回status).

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
   pid_t pc,pr;
   pc = fork();
   if(pc < 0)
        printf("Error occured on forking.\n");
   else if(pc == 0)
   {
       sleep(10);
       exit(0);
   }
   else
   {
       do{
          pr = waitpid(pc,NULL,WNOHANG);
          if(pr == 0)
          {
              printf("No child exited\n");
              sleep(1);
          }
       }while(pr == 1);
       if(pr == pc)
          printf("successfully get child %d\n",pr);
       else
          printf("some error occured\n");
   }
   return 0;
}

结果如下:

关于参数status:

如果参数status的值不是NULL,wait()或者waitpid()会把子进程退出时的状态取出并放入其中,由于这些信息存放在一个整数的不同二进制位中,Linux提供了6个宏用来检查子进程的返回状态;

<1>WIFEXITED(status),如果子进程通过_exit()或者exit()正常退出,则该宏为真,此时可执行WEXITSTATUS(status)获取子进程传送给_exit()或exit()参数的低八位;

<2>WIFSIGNALED(status),如果子进程是由于得到的信号没有被捕捉而导致退出,则为真,此时可通过WTERMSIG(status)取出子进程终止的信号编号;

<3>WIFSTOPPED(status),如果子进程没有终止,但停止并可以重新执行时,返回真,仅出现在waitpid()中使用了WUNTRACED时;

<4>WEXITSTATUS(status),得到status的低八位,即退出码;

<5>WTERMSIG(status),得到导致进程退出的信号编号;

4.进程终止

一般情况下,进程通过exit()或_exit()进行退出;

函数原型:

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

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

exit()和_exit()的区别:

_exit()直接使进程停止运行,而调用exit()之前要检查文件的打开情况,把文件缓冲区的内容写回文件。

/*exittest.c*/
#include <stdio.h>
#include <stdlib.h>

int main()
{
   printf("output begin\n");
   printf("content in buffer");
   exit(0);
}

/*_exittest.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
   printf("output begin!\n");
   printf("content in buffer");
   _exit(0);

结果如下:

时间: 2024-11-10 14:20:11

进程与线程(一)的相关文章

linux查看某个进程的线程id(spid)

鉴于linux下线程的广泛使用 我们怎么查看某个进程拥有的线程id了 现在很多服务的设计 主进程->子进程->线程(比如mysql,varnish) 主进程负责侦听网络上的连接 并把连接发送给子进程 子进程派生线程去处理这些线程 mysql(父进程460,子进程863) 1 460 425 333 ? -1 S 0 0:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/storage/mysql/backup --pid-file=/

该进程或线程自上一个步骤以来已更改

相关解决办法如下: VS进行高度时,断点处提示"该进程或线程自上一个步骤以来已更改" 每个断点跑两遍似的. 这个是由于在VS环境中 勾上了 "工具\选项--->调试--要求源文件与原始版本完全匹配"这个项 该进程或线程自上一个步骤以来已更改 英文表述:The process or thread has changed since last step 以下是是从网上摘录的解决办法. Q: I have a console application that exe

进程和线程、内核空间等概念

用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限.为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间.针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较

进程和线程有什么区别?

作者:榴莲艺声链接:https://www.zhihu.com/question/21535820/answer/22915780来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 一.关于进程和线程,首先从定义上理解就有所不同 1.进程是什么? 是具有一定独立功能的程序.它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序. 2.线程又是什么? 线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的

Python自动化开发,Day10 - 进程、线程、协程

本章内容 操作系统发展史介绍 进程与线程的区别 python GIL全局解释器锁 线程 语法 join 线程锁Lock\Rlock\信号量 将线程变为守护进程 Event时间 Queue队列 生产者消费者模型 进程 语法 进程间通讯 进程池 一.操作系统发展史介绍 手工操作(无操作系统) 1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统,计算机工作采用手工操作方式. 手工操作 程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存

进程、线程和上下文切换

进程是什么? 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed). 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动.它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元. 进程的概念主要有两点:第一,进程是一个实体.每一个进程都有它自己的地址空间,一般情况下,包括文本区域(textregion).数据区域(data region

多线程编程(进程和线程)

多线程编程(进程和线程) 1.进程:指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程可以启动多个线程. 2.线程:指程序中一个执行流程,一个进程中可以运行多个线程. 一.创建线程(两种方式) 二.线程的5种状态( New,Runnable,Running,Block,Dead ): 三.线程的优先级 四.守护线程 /精灵线程/后台线程 五.方法 六.同步代码锁(synchronized) 一.创建线程(两种方式): 方式1:采用继承Thread的方法 第一,继承 Thre

进程与线程

1.进程与程序的区别? 进程与程序的区别:程序是指的存储在存储设备上(如磁盘)包含了可执行机器指 令(二进制代码)和数据的静态实体:而进程可以认为是已经被OS从磁盘加载到内存上的.动态的.可运行的指令与数据的集合,是在运行的动态实体. 2.进程的概念 程序的运行实例(但这并不代表进程就是在运行,根据OS的调度,进程会处于不同的状态),进程是OS进行资源分配的基本单位. 3.线程的概念 线程是进程的一个执行分支,是OS进行调度的基本单位. 4.进程与线程的区别 除了上述概念上的区别外,进程强调资源

走近并发编程之一 进程和线程

并发与并行,进程与线程不仅是操作系统中及其重要的概念,也是并发编程入门 必须要理解的核心知识. 什么是并发?并发与并行的区别 顺序编程:程序中的所有事物在任意时刻都只能执行一个步骤 并发:在同一时间段内,需要处理多个任务,而在每个时间点又只能处理一个,这就是并发. 假设我们要把多个任务分配给处理机,如果这台机器有多个处理器,显然可以同时执行这些任务,这就是并行. 不同于并行,并发的目的旨在最大限度的提高程序在单处理器上的效率.前者是在物理上的同时发生,而并发是在逻辑上的同时发生.如图,如果要在同

程序,进程,线程(需补充)

1.程序,进程,线程 程序:程序时完成特定任务的一系列指令集合. 进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程:线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发