·程序
程序(program)是存放在磁盘文件中的可执行文件·进程
程序的执行实例被称为进程(process)
进程具有独立的权限与职责。如果系统中某个进程崩溃,它不会影响到其余的进程。
每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯。
·进程ID
每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process
lD),进程ID总是一非负整数。PID
task_struct----->结构体------>进程控制块
内核启动特殊例程
启动例程
在进程的main函数执行之前内核会启动该例程放置在/lib/libc.so.**中 在进程的Main函数执行之前内核会启动该例程放置在/lib/libc.so.**中
编译器在编译时会将启动例程编译进可执行文件中启动例程作用 编译器在编译时会将启动例程编译进可执行文件中启动例程作用
搜集命令行的参数传递给main函数中的argc和argv搜集环境信息构建环境表并传递给main函数
登记进程的终止函数 登记进程的终止函数
正常终止:
从main函数返回
调用exit(标准c库函数)调用_exit或_Exit(系统调用)
最后一个线程从其启动例程返回
最后一个线程调用pthread_exit
异常终止:
调用abort
接受到一个信号并终止
最后一个线程对取消请求做处理响应进程返回进程返回:
通常程序运行成功返回0,否则返回非0。在shell中可以查看进程返回值(echo $?)
参数为一个无返回值,无参数的函数指针。
每个启动的进程都默认登记了一个标准的终止函数
终止函数在进程终止时释放进程所占用的一些资源
登记的多个终止函数执行顺序是以栈的方式执行,先登记的后执行。
_exit不调用终止函数
●进程常见状态:
运行状态
系统当前进程就绪状态进程
ps命令的STAT列为值R等待状态
等待事件发生等待系统资源
分为可中断的等待和不可中断的等待
ps命令的STAT列为值S(可中断)D(不可中断)停止状态
ps命令的STAT列为值T僵尸状态
进程终止或结束
在进程表项中仍有记录ps命令的STAT列为值Z
分时系统,分配时间片
●第一步:处理内核中的工作
●第二步:处理当前进程
●第三步:选择进程
实时进程
普通进程
●第四步:进程交换
#include
#includepid_t getpid(void): 获得当前进程lD
uid_t getuid(void):获得当前进程的实际用户ID
uid_t geteuid(void):获得当前进程的有效用户ID
gid_t getgid(void):获得当前进程的用户组ID
pid_t getppid(void):获得当前进程的父进程ID
pid_t getpgrp(void):获得当前进程所在的进程组ID
pid_t getpgid(pid_t pid):获得进程ID为pid的进程所在的进程组ID
#include
#include
pid_t fork(void);
返回:子进程中为0,父进程中为子进程ID,出错为-1pid_t vfork(void);
返回:子进程中为0,父进程中为子进程ID,出错为-1
fork创建的新进程被称为子进程,该函数被调用一次,但返回两次。两次返回的区别是:在子进程中的返回值是0,而在父进程中的返回值则是新子进程的进程ID。
创建子进程,父子进程哪个先运行根据系统调度,且复制父进程的内存空间。 制父进程的内存空间.
vfork创建子进程,但子进程先运行且不复制父进程的内存空间。 创建子进程,但子进程先运行且不复制父进程的内存空间.
fork()的子进程会继承父进程的一些信息
·子进程的继承属性
用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段,共享代码段。
·子进程特有属性
进程ID、锁信息、运行时间、未决信号操作文件时的内核结构变化
子进程只继承父进程的文件描述表,不继承共享文件表项和i-node。父进程创建一个子进程后,文件表项中的引用计数器加1变成2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。
子进程也复制父进程的缓存区
继承父进程的文件描述表,不继承共享文件表项和i-node。
两个进程分别创建一个文件
进程表项大多不复制
代码实例:
#include
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
int count=0;
pid_t pid=0;
if(argc<2){
count=2;
}else{
count=atoi(argv[1]);
}
for(int i=1;i0) break; //父进程退出
}
if(pid>0) wait(NULL);
printf("PID:%d PPID:%d\n",getpid(),getppid());
return 0;
}
代码实例:
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
int count=0;
pid_t pid=0;
if(argc<2){
count=2;
}else{
count=atoi(argv[1]);
}
for(int i=1;i0) wait(NULL);
printf("PID:%d PPID:%d\n",getpid(),getppid());
return 0;
}
僵尸进程:
子进程结束但是没有完全释放内存(在内核中的task_struct没有释放),该进程就成为僵尸进程。当僵尸进程的父进程结束后就会被init进程领养,最终被回收。
避免僵尸进程:
1.让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并回收,调用wait()或者waitpid(),通知内核释放僵尸进程。2.采用信号SIGCHLD通知处理,并在信号处理程序中调用wait函数。、
3.让僵尸进程成为孤儿进程,由init进程回收。
&后台运行
·守护进程(daemon)是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止。
·所有守护进程都以超级用户(用户ID为O)的优先权运行。·
·守护进程没有控制终端
·守护进程的父进程都是init进程
父进程结束,子进程就成为孤儿进程,会由1号进程( init进程)领养。
#include
#include
pid_t wait(int *status);
返回:成功返回子进程ID,出错返回-1
功能:等待子进程退出并回收,防止僵尸进程产生pid_t waitpid(pid_t pid, int *status, int options);
返回:成功返回子进程ID,出错返回-1
功能: wait函数的非阻塞版本
wait和waitpid函数区别
在一个子进程终止前,wait使其调用者阻塞waitpid有一选择项,可使调用者不阻塞。
waitpid等待一个指定的子进程,而wait等待所有的子进程,返回任一终止子进程的状态。
在用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程完全由新程序代换,替换原有进程的正文,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
exec系列函数的注意点
1.execve函数为系统调用,其余为库函数。执行execve函数后面的代码不执行。
2.execlp和execvp函数中的pathame,相对和绝对路径均可使用,其它四函数中的pathname只能使用绝对路径。相对路径一定要在进程环境表对应的PATH中。
3.argv参数为新程序执行main函数中传递的argv参数,最后一个元素为NULL。
4.envp为进程的环境表
●六个函数的都是以“exec”四个字母开头的,后面的字母表示了其用法上的区别:
1、带有字母“I”的函数,表明后面的参数列表是要传递给程序的参数列表,参数列表的第一个参数必须是要执行程序,最后一个参数必须是NULL。2.带有字母“p”的函数,第一个参数可以是相对路径或程序名,如果无法立即找到要执行的程序,那么就在环境变量PATH指定的路径中搜索。其他函数的第一个参数则必须是绝对路径名。
3.带有字母“v”的函数,表明程序的参数列表通过一个字符串数组来
传递。这个数组和最后传递给程序的main函数的字符串数组argv完全一样。第一个参数必须是程序名,最后一个参数也必须是NULL。4.带有字母“e”的函数,用户可以自己设置程序接收一个设置环境变量的数组。
可以是绝对路径,也可以是绝对路径
system函数内部构建一个子进程,由子进程调用exec函数。
等同于/bin/bash -c "cmd"或者exec("bash","-c" ,"cmd");