进程概述

进程:就是进行中的程序
程序:存放指令的程序文件,存放在磁盘上,固定不变的,保存着指令的有序集合。
程序执行过程:将程序从硬盘导入到内存,内存上分为代码区、静态变量区、堆栈区等等
文本区:储存处理器执行的代码
数据区:存储变量和动态分配的内存:全局变量,局部变量,静态变量,
堆栈区:存储着活动进程调用的指令和本地变量,指针变量->栈区,动态分配的内存->堆区
进程为程序执行提供各种各样的条件,程序需要各种各样操作,磁盘操作、网络操作等等。需要各种管理,这种动态的管理行为就可以通俗的称之为进程。所谓程序开始活动起来了。进程是linux系统调度的最小单位


进程状态

进程是程序的执行过程,根据他的生命周期可以分为3种状态。
执行态:该进程正在运行,即进程正在使用CPU
就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理
等待态:进程不能使用CPU,若等待事件发生(等待的资源分配到),将其唤醒

ps

process:os的最小单元,地址空间为4g,其中1gos,另外3g给进程
ps:查看活动的进程
ps -aux:查看各个进程的状态,包括运行、就绪、等待等状态(S睡眠 T中断 R运行 Z僵尸)
ps -aux | grep `aa`:查看指定(aa)进程
ps -ef:查看所有进程的pidppid等信息
ps -aux :查看cpu内存的使用状态


进程标识

进程不能用程序区分
因为不同进程可能执行的是同一个程序
所以,使用ID号来标识区分不同的进程
OS会为每个进程分配一个唯一的整型ID,作为进程的标识号(pid
进程0时调度进程,常被称为交换进程,他不执行任何程序,是内核的一部分,因此被称为系统进程。
进程除了自身ID外,还有父进程ID,每个进程都会有一个父进程,操作系统不会无缘无故产生一个新进程,所有的进程的祖先进程是同一个进程,叫做init进程,进程号是1.init进程是内核自举后的第一个启动进程。
init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。他不是系统进程,但它以系统的超级用户特权运行。
父进程是负责子进程空间的清理


一些概念

并发:宏观上看起来,同时发生,cpu轮转非常快,给人一种错觉,所有的事情在同时发生
并行:同时执行,微观上真的同时执行,多个cpu同时执行不同的进程,多个进程真的在同时执行。
同步:相同的步伐,进程间相互联系,或者共同完成某件事,需要相互协调
异步:不同的步伐,进程间毫无关联


进程的意义

例子:c语言只能让一个任务接一个任务的执行,单片机普通情况也下一样,也是一个接一个的执行任务,除非中断发生,单片机会先中断当前任务的执行,去执行中断需要做的事情,中断执行完毕再回来执行刚才中断的任务。可以拿人来类比,当一个人炒菜的时候,快递打电话来了,让去取快递,不能让送快递的人一直等,只能先不炒菜,去取快递,取完快递再回来继续炒菜。这样实际把炒菜的任务就给耽误了。
linux中,一个人在炒菜,快递打电话来了,让去取快递,他可以叫他的儿子去取快递,自己继续炒菜。从cpu角度想,由于cpu执行速度较快,看起来任务在同时进行(并发执行),这样所有的事情都不耽误。这就是进程的意义,在某个层面上也能说明父进程子进程的意义。
再举一个例子:
ATM机取款时,一方面播放提示音乐,一方面提示密码输入,不带系统的单片机处理起来就很恶心,不能先播音乐,在此期间让用户等待输入,或者播放音乐同时用中断监测用户输入,用户输入会造成音乐的停止。用户体验很差,反过来程序用进程来写效果就很好,子进程播放提示音乐,父进程检测用户输入,父子进程并发执行,可以产生同时运行的效果。提高了用户体验。
说白了进程的意义就是:让任务并发执行


获取进程ID

特殊说明:现在说的进程仅仅是linux下的进程

  • 每个进程都有一个整型的IDPID
  • 每个进程都有一个父进程 PPID
    一个进程想控制其他进程,想与其他进程通信,需要ID
    获取自己的ID
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 

pid_t getpid(void);
功能:获取自己的进程ID
参数:
返回值:本进程的ID


获取自己的父进程的ID

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

pid_t getpid(void);
功能:获取自己的父进程ID
参数:
返回值:本进程的父进程的ID


创建一个进程

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

功能:创建一个子进程
参数:
返回值:有两次,子进程返回0,父进程返回子进程的进程号。
注意:子进程从fork函数之后执行,fork完全拷贝了父进程的地址空间给子进程,父子进程运行顺序不确定。


虚拟创建进程

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

       pid_t vfork(void);

功能:创建一个子进程
参数:
返回值:有两次,子进程返回0,父进程返回子进程的进程号。
注意:子进程从fork函数之后执行,子进程有自己的ID与父进程共享资源,子进程先执行,父进程后执行。价值并不大,不能实现任务并发执行


错误打印函数

#include <stdio.h>
       void perror(const char *s);

功能:打印错误信息(某些函数返回负值,表明发生错误,但是不知道具体类型,使用这个函数可以获得具体错误类型)


销毁进程

进程的常见的终止方式有5种:
主动:

  • main函数的自然返回,注意:return不是结束,只是函数结束,当它刚好结束的是main函数,此时导致进程结束。造成return结束进程的错觉。
  • 调用exit函数 ,标准函数
  • 调用_exit函数 ,系统调用函数
  • 调用abort函数,产生SIGABRT信号

被动:

  • 接收到某个信号,如ctrl+cSIGINTctrl+\ SIGOUT
  • 通过kill 向进程发信号
    前四四种正常的终止,后两种非正常的终止,但无论哪种方式,进程终止都会执行相同的关闭打来的文件,释放占用的内存资源,后两种终止会导致程序有些代码不能正常执行,比如对象的析构、atexit函数的执行。
  • exit__exit函数最大的区别在于exit函数退出之前会检查文件的打开情况,把文件缓冲区的内容写回文件,而__exit直接退出,什么意思?比如打开文件向文件写入内容,如果在文件没有关闭,也没有调用同步到磁盘的函数,文件并没有同步到磁盘,只存在缓冲区内,这时调用exit,那么进程结束时,缓冲区的内容可以同步到文件中,内容已经存在在文件之中了,调用__exit进程直接结束,文件不会有写入的内容。

启动新进程

#include <stdlib.h>
       int system(const char *command);

功能:打开命令或者程序
参数:带路径的程序启动文件,或者在启动变量里声明的程序直接写程序名
返回值:-1失败
打开的程序是另一个进程,也可以成为此程序的子进程,因此子进程不一定和父进程视同一个程序,在成功打开所要执行的文件之后,父进程才能继续执行。
父进程程序

#include <stdlib.h>
#include <stdio.h>
int main(void)
{
  printf("调用子进程\n");
    system("./son");
    printf("调用子进程结束\n");
    return 0;
}

子进程程序

#include <stdio.h>
#include <unistd.h>
int main(void)
{
   printf("这是子进程在执行\n");
    sleep(20);
}

程序运行效果

[root@CentOS workdir]# gcc son.c -o son
[root@CentOS workdir]# gcc sys.c -o app
[root@CentOS workdir]# ./app
调用子进程
这是子进程在执行
调用子进程结束

进程调度结果,son父进程指向app

root      39055  35664  0 19:28 pts/0    00:00:00 ./app
root      39056  39055  0 19:28 pts/0    00:00:00 ./son

进程替换,exec函数族

 #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

重点学习四种

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

这四个函数第一个参数都是可执行程序或者脚本的程序名,execlexecv需要带有完整的路径,第二参数为任意字符,起到占位作用,第三个或者后面的字符为调用者的参数,参数列表最后以NULL结尾,而execlpexecvp不需要只需要带有可执行程序的名称即可,系统自动去环境变量去寻找同名文件,execlexeclp需要NULL结尾.

函数后缀说明:
l v:参数呈现形式
l:list 参数一个个的列出来
vvector 参数用数组存储起来
p:目标程序,可以省略路径
e:环境变量,不考虑

system程序稍作修改
父进程程序

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
  printf("当前进程id:%d\n",getpid());
  printf("调用son进程\n");
  execl("./son",NULL);  
  printf("调用子进程结束\n");
    return 0;

}

子进程程序

#include <stdio.h>
#include <unistd.h>
int main(void)
{
   printf("这是子进程在执行\n");
   printf("son进程id:%d\n",getpid());
   sleep(5);
}

程序运行效果,不会打印调用子进程结束,父子进程ID号相同,表明当前进程被son进程替换

[root@CentOS workdir]# gcc sys.c 
[root@CentOS workdir]# gcc son.c -o son
[root@CentOS workdir]# ./a.out 
当前进程id:39606
调用son进程
这是子进程在执行
son进程id:39606
[root@CentOS workdir]# 

注意:当时用参数list的函数时,最后一个参数赋值NULL,表明没有参数,否则运行会出错。


wait进程同步

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

pid_t wait(int *status);
函数返回值为结束子进程的进程号,当前进程中没有子进程则返回-1,参数为子进程结束状态指针,如果单纯想等待子进程的结束而不关心进程结束状态,参数写入NULL即可;若想获得子进程结束状态,将参数地址写入即可,例如定义int statue存储子进程的结束状态,函数调用wait(&statue)即可;
一旦函数调用wait(),就会立即阻塞自己并自动判断当前进程中是否有某个子进程退出,wait()返回该子进程的状态,终止子进程的ID,所以它总能了解是哪一个子进程终止了。
pid_t waitpid(pid_t pid, int *status, int options);
pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid==-1时,等待任何一个子进程退出,没有任何限制,此时waitpidwait的作用一模一样。
pid==0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
第二个参数与wait相同,存储制定子进程终止的状态信息。为整形指针类型。
options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANGWUNTRACED两个选项,这是两个常数,可以用”|”运算符把它们连接起来使用。
WNOHANG:pid指定进程并不是立即可用(终止状态),waitpid不阻塞,返回值为0.
WUNTRACED:若实现支持作业控制··· ···涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多说了。
稍微琢磨一下就能看出,waitwaitpid有很大的关联性甚至wait就是封装了wairpid函数,没错!实际就是这样
察看/include/unistd.h文件349-352行就会发现以下程序段:

    static inline pid_t wait(int * wait_stat)  
    {  
        return waitpid(-1,wait_stat,0);  
    }  

返回值和错误
waitpid的返回值比wait稍微复杂一些,一共有3种情况:

  • 当正常返回的时候,waitpid返回收集到的子进程的进程ID
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD
Logo

更多推荐