问1:什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c -o pro. 磁盘中生成pro文件,叫做程序
进程是动态的概念,指的是程序的一次运行活动,通俗来说就是程序跑起来了,系统中就多了一个进程
程序:
(1)存放在磁盘上的指令和数据的有序集合(文件)
(2)静态的
进程:
(1)执行一个程序所分配资源的总称
(2)进程是程序的一次执行过程
(3)动态的,包括创建、调度、执行和消亡
理解:cpu不能直接访问磁盘,要先把程序中的内容(指令和数据)加载到内存中,这样CPU才能去执行指令,才能获取到数据,因此要执行程序,要分配内存空间,分配CPU资源,这些资源的总称即为进程
一个程序执行的时候会创建多个进程
问2.什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证,可以和之前的文件描述符fd一样理解
Pid = 0; //交换进程(swapper)
作用:进程调度
Pid = 1;//init进程
作用:系统初始化
编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符
通过系统数据段,可以使得操作系统有效的管理进程,系统数据段中主要包含:进程控制块
、CPU寄存器值
、堆栈
1、进程控制块(pcb)
作用:存放进程的属性
有:
(1)进程标识PID:类似于人的身份证
(2)进程用户:通过用户检查是否具有某种权限
(3)进程状态、优先级:决定进程调度顺序以及进程分配时间片的长短
(4)文件描述符表:表明打开了哪些文件
2、CPU寄存器值
每个进程会保存他用到寄存器的值
比如:pc寄存器(程序计数器)
用于存放进程下一条指令的地址
当进程的时间片用完,他就会让出CPU,等到下次被调度执行的时候,进程不会重新开始执行,而是会从上一次停止的地方开始继续往下执行,因此就需要从pc中取出下一条指令执行的地址。
3、堆栈
操作系统会用到进程栈,所有的局部变量,函数的参数等都会用到
1.交互进程
在下shell启动时,通过终端与用户交互。用户通过终端输入,进程进行接受并处理,并将结果打印在终端上。是一种可以在前台运行,也可以在后台运行
前台进程可以从终端输入,也可以从终端输出
后台进程无法从终端读取输入,但可以往终端输出
2.批处理进程
和终端无关,被提交到一个作业队列中以便顺序执行
批处理进程一般是管理员经常用到,开发人员很少用到
3.守护进程(重要)
和终端无关,一直在后台运行
很多服务器都是以守护进程的形式在运行
系统关闭,守护进程才关闭,生命周期很长
(1)运行态
,也分为正在运行的运行态和准备运行的准备态
(2)等待态
,也成阻塞态,休眠态,指的是进程等待一个事件的发生或某种系统资源
当有资源的时候,操作系统会唤醒进程,进程又会回到运行态
也分为不可中断和可中断,通过会不会被信号打断来进行区分
(3)停止态
,进程被中止,收到信号后可继续执行
最常用的是GDB去调试一个程序的时候,设置断点,运行到断点时候会停下来,停下来的原因就是GDB向进程发了一个中止信号。
(4)死亡态
,也叫僵尸态,已经终止的进程,但是pcb(放了进程返回的值以及进程结束的一些信息)没有被释放,其他资源释放了
进程状态图如下:
1.ps
作用:查看系统进程快照
(1)ps -ef:查看所有进程的简要信息
(2)ps aux:还能显示进程当前状态
进程状态:
R:运行态
S:等待态
Z:僵尸态
+:表示为前台进程
两种用法都可以配合管道进行过滤 |grep
2.top
作用:查看进程动态信息,可以查看那些进行最占用资源,每个3s进行一次统计
3./proc
作用:查看进程详细信息,proc即目录的意思
在该目录下查看进程ID号,再进入进程对应目录查看
1.nice
作用:按**用户指定
**的优先级运行进程
当使用yop命令时候可以看到有NI这一列,范围为-20~19,默认为0,NI的数值越小代表优先级越高。
例:nice -n 2 ./test
其中:2就是指定优先级,普通用户只能指定正整数,管理员用户可以任意指定。
2.renice
作用:改变正在运行
的进程的优先级
例:renice -n 2 xxx
其中,xxx为要修改进程的进程号,普通用户只能降低优先级,管理员用户可以降低,也可以提高。
3.jobs
作用:查看后台进程(后台作业)
例:./test &为运行一个后台进程
得到[1] 29140
[1]为作业号,29140为进程号
再使用jobs命令就可以进行查看
4.bg
作用:将挂起的进程在后台运行
5.fg
作用:将后台运行的进程放到前台运行
例:
fg 1:将一个后台的进程放到前台
ctrl+z:让当前前台进程在后台挂起
bg 2:让后台作业在后台运行起来
使用fork函数创建一个进程
pid_t fork(void);
fork函数调用成功,返回两次
成功时父进程返回子进程的ID号,子进程返回0
返回值为0:代表当前进程为子进程
返回值为非负数:代表当前进程为父进程
返回值为-1:调用失败
fork创建一个子进程的一般目的
:
(1)一个父进程希望复制自己,使父,子进程执行不同的代码段。这在网络服务进程中是常见的------父进程等待客户端的服务请求。在这种请求到达时,父进程调用fork,使子进程处理此请求,父进程继续等待下一个请求到达。
(2)一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec
例子:
getpid():获取当前进程,与pid进行比较
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("pid = %d\n",getpid());
pid = fork();
if(pid>0){
printf("this is father pid,father pid is %d\n",getpid());
}else if(pid == 0){
printf("this is child pid,child pid is %d\n",getpid());
}else{
printf("fork error\n");
}
return 0;
}
运行结果:
父子进程:
(1)子进程继承了父进程的内容
(2)父子进程有独立的地址空间,互不影响
(3)若父进程先结束,则子进程会变成孤儿进程,被init进程收养,子进程变成后台进程
(4)若子进程先结束,父进程没有及时回收的话,子进程会变成僵尸进程
思考:
(1)子进程从何处开始运行?
答:从fork()后的下一条语句开始开始执行,注意:子进程并没有执行fork(),因为如果子进程也执行了fork,最终会进去一个死循环,不断创建新的进程。
(2)父进程创建子进程后,父子进程谁先执行?
答:不确定
。对于Linux来说,并没有进行相应的规定,最终是看内核的调度,内核先调度谁,谁就先运行。
对上面两个问题的总结即是第三个问题
(3)fork创建的时候发生了什么?
数据段,堆,栈拷贝
代码段共享
父子进程谁先跑,由进程调度决定
验证代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
int a = 10;
printf("pid = %d\n",getpid());
pid = fork();
if(pid>0){
printf("this is father pid,father pid is %d\n",getpid());
}else if(pid == 0){
printf("this is child pid,child pid is %d\n",getpid());
a = a + 10;
}else{
printf("fork error\n");
}
printf("a = %d\n",a);
return 0;
}
运行结果:
vfork函数也可以创建进程,与fork的区别:
1.vfork直接使用父进程存储空间,不拷贝
2.vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
实例代码:
#include<stdio.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0){
while(1){
printf("cnt = %d\n",cnt);
printf("this is father pid,father pid = %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child pid,child pid = %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(0);
}
}
}
return 0;
}
运行结果:
子进程线运行三次结束后,才运行父进程
正常退出:
1.main函数调用return退出
2.进程调用exit(),属于标准c库
3.进程调用_exit()或者 _Exit(),属于系统调用
补充:
1.进程包含多个线程,进程最后一个线程返回
2.最后一个线程调用pthread_exit
异常退出:
1.调用abort
2.当进程收到某些信号时,如Ctrl+C
3.最后一个线程对取消请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
1.当子进程退出状态不被收集,会变成僵死进程(僵尸进程),代码与前面一样
1 #include<stdio.h>
2 #include <unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t pid;
7 int cnt = 0;
8 pid = vfork();
9 if(pid > 0){
10 while(1){
11 printf("cnt = %d\n",cnt);
12 printf("this is father pid,father pid = %d\n",getpid());
13 sleep(1);
14 }
15 }
16 else if(pid == 0){
17 while(1){
18 printf("this is child pid,child pid = %d\n",getpid());
19 sleep(1);
20 cnt++;
21 if(cnt == 3){
22 exit(0);
23 }
24 }
25 }
26 return 0;
27 }
s+:正在运行的状态
z+:僵尸进程
2.父进程等待子进程退出,并收集子进程的退出状态
正常退出时,会有退出码,即exit中的参数为几,根据子进程的退出码来判断子进程的完成情况
pid_t wait(int *status);
status:整形数指针
非空:子进程退出状态放在它所指的地址中
空:不关心退出状态
实例代码:
1 #include<stdio.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include<stdlib.h>
5 int main()
6 {
7 pid_t pid;
8 int status = 10;
9 int cnt = 0;
10 pid = vfork();
11 if(pid > 0){
12 wait(&status);
13 printf("child quit,child status = %d\n",WEXITSTATUS(status));
14 while(1){
15 printf("cnt = %d\n",cnt);
16 printf("this is father pid,father pid = %d\n",getpid());
17 sleep(1);
18 }
19 }
20 else if(pid == 0){
21 while(1){
22 printf("this is child pid,child pid = %d\n",getpid());
23 sleep(1);
24 cnt++;
25 if(cnt == 3){
26 exit(3);//退出码为3
27 }
28 }
29 }
30 return 0;
31 }
(1)如果其所有子进程都还在运行,则阻塞
(2)如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
(3)如果它没有任何子进程,则立即出错返回
引入waitpid
,区别:
wait使地调用者阻塞,waitpid有一个选项可以使调用者不阻塞
pid_t waitpid(pid_t pid,int *status,int options);
pid == -1:等待任一子进程,与wait等效
pid > 0:等待其进程ID与pid相等的子进程 //用的较多
pid == 0:等待其组ID等于调用进程ID的任一子进程
pid < -1:等待其组ID等于pid绝对值的任一子进程
3.孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程就叫做孤儿进程
linux避免系统村存在过多的孤儿进程,init进程(进程ID为1,是系统的初始化进程)收留孤儿进程,变成孤儿进程的父进程
(1)进程调用exec函数族执行某个程序
(2)进程当前内容被指定的程序替换
(3)实现父子进程执行不同的程序
实现:父进程创建子进程,子进程调用exec函数族,从而去执行指定的程序,而父进程不会受到影响
这在shell中是很常见的,shell相当于一个父进程,shell创建的子进程会去调用执行用户指定的程序。
博文推荐:exec族函数用法
exec实例代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 //函数原型:int execl(const char *path, const char *arg, ...);
6
7 int main(void)
8 {
9 printf("before execl\n");
10
11 char *argv[] = {"ps",NULL,NULL};
12 // if(execvp("ps",argv) == -1)
13 if(execv("/bin/ps",argv) == -1)
14 {
15 printf("execl failed!\n");
16
17
18 perror("why?");
19
20 }
21 printf("after execl\n");
22 return 0;
23 }
int system(const char* command);
system()函数的返回值:
成功:返回进程的状态值
当sh不能执行时:返回127
失败:返回-1
实例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
if(execv("ps") == -1) //直接ps
{
printf("execl failed!\n");
perror("why?");
}
printf("after execl\n");
return 0;
}
与exec不同的是最后还是会返回到源程序当中,执行后面的代码
推荐博文:Linux system函数详解
相比于system的好处:popen可以获取运行的输出结果
FILE *popen(const char *command,const char *type);
参数说明:
command
: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
type
: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
返回值:
如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断
int pclose (FILE* stream)
参数说明:
stream:popen返回的文件指针
返回值:
如果调用失败,返回 -1
popen执行某个程序,例如popen(“ps”,“r”);会把这个结果重定位到管道中,将数据往管道里面扔,管道的另外一边调用fread来把数据读到缓冲区
实例代码:
#include <stdio.h>
#include <string.h>
int main(void)
{
FILE *fp;
char buf[10240] = {0};
fp = popen("ps","r");
fread(buf, 10240, 1, fp);
printf("%s\n",buf);
pclose(fp);
return 0;
}
博文推荐:linux下popen使用心得
1.守护进程特点
1.1相关介绍:
(1)守护进程是Linux下三种进程类型之一
Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性。
交互进程
是由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。批处理进程
和终端没有联系,是一个进程序列。监控进程
(也称系统守护进程
)是Linux系统启动时运行的进程,并常驻后台。例如,httpd是著名的Apache服务器的监控进程。(2)通常在系统启动时运行,系统关闭时结束
(3)守护进程在Linux中大量使用,很多服务程序都是以守护进程形式运行
1.2特点
(1)始终在后台运行
(2)独立于任何终端(与交互进程的最大区别)
(3)周期性的执行某种任务或等待处理特定事件
2.会话,控制终端
(1)Linux以会话、进程组的方式管理进程
(2)每个进程属于一个进程组
(3)会话是一个或多个进程的集合
会话中运行的第一个进程是shell,因此shell也叫作会话的首进行
(4)一个会话最多为只能打开一个控制终端
(5)终端关闭时,所有相关的进行都会结束
3.守护进程创建(5步)
(1)创建子进程,父进程退出
if(fork() > 0){
exit(0);
}
子进程变成孤儿进程
子进程在后台运行
(2)子进程创建新会话
if(setsid() < 0){
exit(-1);
}
子进程变成新的会话组长
子进程脱离原来的终端
(3)更改当前工作目录
chdir('/');
chdir("/tmp");
参数为:指定修改的目录
守护进程一直在后台运行,其工作目录不能被卸载
重设当前目录为cwd
(4)重设文件权限掩码
if(umask(0) < 0){
exit(-1);
}
文件权限掩码设置为0
只影响当前进程
(5)关闭打开的文件描述符
int i;
for(i=0;i<getdtablesize();i++){
close(i);
}
getdtablesize返回当前进程打开文件的最大个数
关闭所有从父进程继承的打开文件
已经脱离终端,stdin/stdout/stderr无法再使用
错误描述: 在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报...
工具:Eclipse,Oracle,smartupload.jar;语言:jsp,Java;数据存储:Oracle。...
复制代码 代码如下: % URL="http://news.163.com/special/00011K6L/rss_newstop....
项目中用到的一些特殊字符和图标 html代码 XML/HTML Code 复制内容到剪贴板 div ...
上篇文章给大家介绍了 Java正则表达式匹配,替换,查找,切割的方法 ,接下来,...
DELETEFROMTablesWHEREIDNOTIN(SELECTMin(ID)FROMTablesGROUPBYName) Min的话保...
正则忽略大小写 – RegexOptions.IgnoreCase 例如: 复制代码 代码如下: Str = R...
Elasticsearch 是通过 Lucene 的倒排索引技术实现比关系型数据库更快的过滤。特...
4月11日20:30~22:00通过腾讯会议进行了第二次在线学习讨论我把学习笔记整理一下...
本文实例讲述了Laravel框架源码解析之反射的使用。分享给大家供大家参考,具体如...