? ? ? ?距离上一次利用高并发技术实现360度行车记录仪功能已经过去半年了。开始写一系列关于系统编程和网络编程内容进行总结。
? ? ? ?温故而知新,欢迎大家讨论学习。
????一些比较基本的概念,如果学过操作系统,这些内容都很常见,可以适当跳过…
- 单道程序设计
- 多道程序设计
- 微观串行、宏观并行 时间片
- 同步 和 异步
- 并发和并行
相关术语参考链接(重)
????“程序(Program)”是一个静态的概念,一般对应于操作系统中的一个可执行文件
????执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。比如:我们在用酷狗听音乐,也可以使用eclipse写代码,也可以同时用浏览器查看网页。
????并发是多个任务交替执行的,多个任务之间可能还是串行的。所有的并发处理都有排队等候,唤醒和执行这三个步骤。所以并发是宏观的观念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片轮转),所以在宏观上多个几乎同时到达的请求同时在被处理。如果是同一时刻到达的请求也会根据优先级的不同,先后进入队列排队等候执行。并发针对的是多个请求,比如:一个CPU,一个web服务,同时涌入多个请求,CPU需要交替切换的执行多个请求,而不是一个请求执行完成之后再执行下一个请求。并发的实质是一个物理CPU(也可以是多个物理CPU)在若干个程序之间多路复用,并发性是对有限物理资源强制行使 多用户共享以提高效率。
参考链接
????虚拟内存:虚拟内存是一种逻辑上扩充物理内存的技术。基本思想是用软、硬件技术把内存与外存这两级存储器当做一级存储器来用。虚拟内存技术的实现利用了自动覆盖和交换技术。简单的说就是将硬盘的一部分作为内存来使用。
????虚拟地址空间:在32位的i386 CPU的地址总线的是32位的,也就是说可以寻找到4G的地址空间。我们的程序被CPU执行,就是0x000000000xFFFFFFFF这一段地址中。高1G的空间为内核空间,由操作系统调用,低3G的空间为用户空间,由用户使用。
????CPU在寻址的时候,是按照虚拟地址来寻址,然后通过MMU(内存管理单元)将虚拟地址转换为物理地址。因为只有程序的一部分加入到内存中,所以会出现所寻找的地址不在内存中的情况(CPU产生缺页异常),如果在内存不足的情况下,就会通过页面调度算法来将内存中的页面置换出来,然后将在外存中的页面加入到内存中,使程序继续正常运行。
原图链接
????我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是 task_struct 结构体。
????/usr/src/linux-headers-3.16.0-30/include/linux/sched.h
文件中可以查看 struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:
补充
文件id查看指令:
ps aux
????进程基本的状态有 5 种。分别为新建态,就绪态,运行态,阻塞态与终止态。其中新建态为进程准备阶段,常与就绪态结合来看。
引起进程状态转换的具体原因如下:
?
pid_t fork(void);
失败返回-1;成功返回:① 父进程返回子进程的 ID(非负) ②子进程返回 0pid_t getpid(void);
获取当前进程 IDpid_t getppid(void);
获取当前进程的父进程 ID补充:初学者常常有个问题,就是子进程的执行范围:其实子进程只会继续执行fork函数之后的部分…
fork getpid getppid的使用
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid ;
pid = fork();
if(pid<0)
{
perror("fork error");
}
else if(pid==0)
{
printf("I am child pid =%d my father pid=%d\n",getpid(),getppid());
}
else
{
printf("I am father pid =%d my father pid =%d\n",getpid(),getppid());
}
return 0;
}
通过命令行参数指定创建进程的个数,每个进程休眠 1S 打印自己是第几个被创建的进程。
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid;
int i;
for(i=0;i<5;i++)
{
pid =fork();
if(pid==0)
{
break;
}
}
if(5==i)//父进程 等待for结束自己台跳出
{
sleep(5);
printf("I am father\n");
}
else//子进程 提前break跳出 打印
{
sleep(i);
printf("I am %d child \n",i+1);
}
return 0;
}
相同部分:全局变量、.data、.text、栈、堆、环境变量、用户 ID、宿主目录、进程工
作目录、信号处理方式…
不同部分:1.进程 ID 2.fork 返回值 3.父进程 ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
值得注意的是:
????父子进程间遵循读时共享写时复制的原则。
????这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
????而不是简单的复制0-3G用户空间内容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int a = 10;
pid_t pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) { // 子进程
a=a+10;
printf("---child is created\n");
printf("a=%d\n",a);
} else if (pid > 0) { // 父进程
printf("---parent process: my child is %d\n", pid);
printf("a=%d\n",a);
}
printf("===================end of file\n"); // 父子进程各自执行一次.
return 0;
}
在项目中也没有用到,也不做重点记录了,需要的时候补充。
????fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。
????将当前进程的.text、.data 替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程 ID 不变,换核不换壳。
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 execve(const char *path, char *const argv[], char *const envp[]);
????孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为 init 进程领养孤儿进程。
ps ajx
# include<stdio.h>
# include<stdlib.h>
# include<unistd.h>
int main()
{
pid_t pid ;
pid = fork();
if(pid<0)
{
perror("fork error");
}
else if(pid==0)
{
while(1)
{
printf("I am child pid =%d my father pid=%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("I am father pid =%d \n",getpid());
sleep(4);
printf("I am died\n");
return 0;
}
return 0;
}
????僵尸进程: 子进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量$?查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
作用:
原型:
pid_t wait(int *status);
WIFEXITED(status)
宏判断为真 表示程序正常退出WEXITSTATUS(status)
上一个宏判断为真 则返回状态值WIFSIGNALED(status)
宏判断为真 表示程序异常退出WTERMSIG(status)
上一个判断为真,则返回状态值#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid,wpid;
int status;
pid =fork();
if(pid==0)
{
printf(" i am child,my id is%d\n",getpid());
printf("child die\n");
return 73;
}
else if(pid>0)
{
wpid=wait(NULL);//不关心怎么结束的
wpid = wait(&status);//等待子进程结束
if(wpid==-1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))//判断 子进程正常退出判断
{
printf("child exit with%d\n",WEXITSTATUS(status));
printf("------parent finish\n");
}
if(WIFSIGNALED(status))//判断 子进程异常退出判断
{
printf("child exit with%d\n",WTERMSIG(status));
}
}
else
{
perror("fork");
return 1;
}
}
作用:
作用同 wait,但可指定 pid 进程清理,可以不阻塞。
原型:
pid_t waitpid(pid_t pid, int *status, in options);
成功:返回清理掉的子进程 ID;失参数pid
0 回收指定 ID 的子进程
参数三
注意:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 循环期间, 子进程不 fork
break;
}
if (i == 2) {
tmpid = pid; //子进程pid
printf("--------pid = %d\n", tmpid);
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
// sleep(5);
//wait(NULL); // 一次wait/waitpid函数调用,只能回收一个子进程.
//wpid = waitpid(-1, NULL, WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
//wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞等待
printf("i am parent , before waitpid, pid = %d\n", tmpid);
//wpid = waitpid(tmpid, NULL, WNOHANG); //指定一个进程回收, 不阻塞
wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
????这段代码从结果上可以看出做不到回收指定的进程。两段代码的区别在于子进程id的存放的方式。
????在这段代码中,我们在子进程中做了pid = getpid();
父子进程资源不共享导致的是父进程不知道这个pid
的值。
而在上一段代码中,我们直接通过pid = fork();tmpid = pid;
这样的方式在父进程中成功保存子进程的进程号。
//指定回收一个子进程错误示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
if (fork() == 0) { // 循环期间, 子进程不 fork
if (i == 2) {
pid = getpid();
printf("------pid = %d\n", pid);
}
break;
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
sleep(5);
printf("------in parent , before waitpid, pid= %d\n", pid);
wpid = waitpid(pid, NULL, 0); //指定一个进程回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
// 回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 循环期间, 子进程不 fork
break;
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
while((wpid=waitpid(-1,NULL,0))>0)
{
printf("I'm parent,wait a child finish:%d\n",wpid);
}
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
kill -9 父进程号
如有错误欢迎指出…
一、属性列表 复制代码 代码如下: color : #999999 文字颜色 font-family : 宋体...
0、前言 网友提问如下: 本地进程之间 pipe shm msg 消息队列, sem 两个pc之间 ...
在任何网站注册用户的时候,都会检查用户是否已经存在。很久以前的处理方式是将...
一般现今ASP木马常通过以下四点来操作服务器,所以我们只要将一下四处设置好就能...
需求: 实现点击按钮后,数据以表单形式提交至服务器,并接收来自服务器的返回数...
本文实例讲述了Laravel框架源码解析之反射的使用。分享给大家供大家参考,具体如...
大家好,我是IT共享者,人称皮皮。 前几天给大家分享了一篇关于百度网盘的下载神...
当用户在应用市场里下载APP体验了免费的基础功能和服务后通过自主选择付费以获取...
索引类似大学图书馆建书目索引,可以提高数据检索的效率,降低数据库的IO成本。M...
本系列将按照类别对题目进行分类整理,重要的地方标上星星,这样有利于大家打下...