第六天
程序和进程
1、程序
是指编译好的二进制文件,在磁盘上,不占用系统资源,包括CPU、内存、打开的文件、设备、锁等等
2、进程
是一个抽象的概念,与操作系统原理联系紧密。进程是高度活跃的程序,占用系统资源。在内存中执行。(程序运行起来就产生一个进程)
并发
在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态,但在任意一个时间点上,仍然只有一个进程在运行。例如:计算机可以边听音乐边上网聊天,音乐播放器是一个进程,聊天软件是一个进程,因为并发,所以它们可以同时运行。
CPU
CPU的核心功能单元
1、寄存器
CPU内部的告诉储存器,常见储存器有add,exa,ebx等等,一些寄存器只能用于特定的用途,比如eip用作程序计数器,称为特殊寄存器,而另一些寄存器可以用在各种运算和读写内存的指令中。
2、程序计数器
一种特殊寄存器,保存着CPU取下一条指令的地址,CPU按程序计数器保存的地址去内存中取指令然后解释执行,这时程序计数器保存的地址会自动加上该指令的长度,指向内存中的下一条指令。
3、译码器
4、算术逻辑单元(ALU)
如果译码器将一条指令解释为运算指令,就调动算术逻辑单元去做运算。
5、MMU(内存管理单元)
现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要处理器中的MMU(Memory Management Unit,内存管理单元)提供支持。
1)MMU的功能
MMU的两个主要功能:
1、虚拟内存与物理内存的映射
2、设置修改内存访问级别
进程控制块PCB
每个进程在内核中都有一个进程控制块来维护进程相关的信息,linux内核的进程控制块是一个task_struct结构体。下面是一些部分:
1、进程id
系统中每个进程都有唯一的id,在C语言中用pid_t类型表示,是一个非负整数。
2、进程的状态
分别为初始态、就绪态、运行态、挂起态和终止态
环境变量
指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:
① 字符串
② 有统一的格式: 名 = 值[:值]
③ 值用来描述进程环境信息
循环创建多个子进程
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 int main()
6 {
7 int i;
8 pid_t pid;
9 printf("xxxxxxxx\n");
10
11 for(i = 0;i < 5; i++)
12 {
13 pid = fork();
14 if(pid == -1)
15 {
16 perror("fork error:");
17 exit(1);
18 }
19 else if(pid == 0)
20 {
21 break;//key
22 // printf("I'm %dth child ,pid = %u,ppid = %u\n",i+1,getpid(),getppid());
23 }
24 }
25 if(i < 5)
26 {
27 sleep(i);
28 printf("I'm %dth child, pid = %u\n",i+1, getpid());
29 }
30 else
31 {
32 sleep(i);
33 printf("I'm parent");
34 }
35 printf("yyyyyyyyyyyy\n");
36 return 0;
37
38 }
父子进程共享
父子进程间遵循读时共享写时复制的原则。
1、相同处
全局变量、.date、.text、栈、堆、环境变量、用户ID、宿主ID、进程工作目录、信号处理方式...
2、不同处
进程ID、fork返回值、父进程ID、进程运行时间、闹钟、未决信号集
第七天
exec函数
execlp
加载一个进程,借助PATH环境变量
int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1
参数1 : 要加载的程序的名字。
该函数通常调用系统程序例如:ls date cp
execl
加载一个进程, 通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
execvp
加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式: ①... ② argv[] (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, ...))
变参终止条件:① NULL结尾 ② 固参指定
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 pid_t pid;
7 pid = fork();
8 if(pid == -1)
9 {
10 perror("fork ");
11 exit(1);
12 }
13 else if(pid > 0)
14 {
15 sleep(1);
16 printf("parent\n");
17 }
18 else
19 {
20 //execl("./while","while",NULL);
21 //execlp("ls", "ls","-l","-a",NULL);
22 char *argv[] = {
"ls","-l","-a","-h",NULL};
23 execv("/bin/ls",argv);
24 }
25 return 0;
26 }
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<fcntl.h>
5 int main()
6 {
7 int fd;
8 fd = open("ps.out", O_WRONLY|O_CREAT|O_TRUNC,0644);
9 if(fd < 0)
10 {
11 perror("open ps.out");
12 exit(1);
13 }
14 dup2(fd, STDOUT_FILENO);//dup2(3,1);
15 execlp("ps","ps","aux",NULL);//no successful return
16 //close(fd);
17 return 0;
18 }
回收子进程
1、孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
2、僵尸进程
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
3、wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
**pid_t wait(int *status);** 成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 int main()
6 {
7 int status;
8 pid_t pid,wpid;
9 pid = fork();
10 if(pid == 0)
11 {
12 printf("I'm child,my parent = %d,going to sleep 10s\n",getppid());
13 sleep(100);
14 printf("child die\n");
15 exit(76);
16 }
17 else if(pid > 0)
18 {
19 wpid = wait(&status);
20 if(wpid == -1)
21 {
22 perror("wait ");
23 exit(1);
24 }
25 if(WIFEXITED(status))
26 {
27 printf("child exit with %d\n",WEXITSTATUS(status));
28 }
29 if(WIFSIGNALED(status))
30 {
31 printf("child killed by%d\n",WTERMSIG(status));
32 }
33 while(1)
34 {
35 printf("I'm parent,pid = %d,myson =%d\n",getpid(),pid);
36 sleep(1);
37 }
38 }
39 else
40 {
41 perror("fork ");
42 exit(1);
43 }
44 return 0;
45
46 }
4、waitpid函数
pid_t waitpid(pid_t pid, int *status, in options); 成功:返回清理掉的子进程ID;失败:-1(无子进程)
特殊参数和返回情况:
参数pid:
> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/wait.h>
4 #include<unistd.h>
5 int main(int argc, char **argv)
6 {
7 int n = 5,i;
8 pid_t pid;
9 pid_t wpid;
10 int q,p;
11 if(argc == 2)
12 {
13 n = atoi(argv[1]);
14 }
15 for(i = 0; i < 5; i++)
16 {
17 p = fork();
18 if(p == 0)
19 {
20 break;
21 }
22 else if(i == 3)
23 {
24 q = p;
25 }
26 }
27 if(n == i)
28 {
29 sleep(n);
30 printf("I'm parent,pid = %d\n",getppid());
31 do
32 {
33 wpid = waitpid(-1, NULL, WNOHANG);
34 if(wpid > 0)
35 {
36 n--;
37 }
38 sleep(1);
39 }
40 while(n > 0);
41 printf("wait finish\n");
42 //while(waitpid(-1, NULL, WNOHANG));//wait(NULL);
43 while(1);
44 }
45 else
46 {
47 sleep(i);
48 printf("I'm %dth child, pid = %d, gpid = %d\n",i+1, getpid(),getppid());
49 }
50 return 0;
51 }
管道
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
① 数据自己读不能自己写。
② 数据一旦被读走,便不在管道中存在,不可反复读取。
③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
④ 只能在有公共祖先的进程间使用管道。
常见的通信方式有,单工通信、半双工通信、全双工通信
pipe函数
int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<fcntl.h>
6 int main()
7 {
8 pid_t pid;
9 int fd[2];
10 int ret = pipe(fd);
11 if(ret == -1)
12 {
13 perror("pipe ");
14 exit(1);
15 }
16 pid = fork();
17 if(pid == -1)
18 {
19 perror("fork ");
20 exit(1);
21 }
22 else if(pid == 0)//子 读数据
23 {
24 close(fd[1]);//关闭写端
25 char buf[1024];
26 ret = read(fd[0], buf, sizeof(buf));
27 if(ret == 0)
28 {
29 printf("------\n");
30 }
31 write(STDOUT_FILENO, buf, ret);
32 }
33 else//写
34 {
35 close(fd[0]);//关闭读端
36 write(fd[1], "hello pipe\n", strlen("hello pipe\n"));
37 }
38 return 0;
39 }
setpgid函数
int setpgid(pid_t pid,pid_t pgid);
函数作用:将pid进程的进程组ID设置成pgid,创建一个新进程组或加入一个已存在的进程组
函数性质:
性质1:一个进程只能为自己或子进程设置进程组ID,不能设置其父进程的进程组ID。
性质2:if(pid == pgid), 由pid指定的进程变成进程组长;即进程pid的进程组ID pgid=pid。
性质3:if(pid==0),将当前进程的pid作为进程组ID。
性质4:if(pgid==0),将pid作为进程组ID。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 pid_t pid;
7 if((pid = fork()) < 0)
8 {
9 perror("fork error");
10 exit(1);
11 }
12 else if(pid == 0)
13 {
14 printf("child pid = %d\n",getpid());
15 printf("child group id = %d\n", getpgid(0));
16 sleep(3);
17 printf("group id of child is changed to %d\n",getpgid(0));
18 exit(1);
19 }
20 else if(pid > 0)
21 {
22 sleep(1);
23 setpgid(pid, pid);//让子进程成为进程祖组长
24 sleep(5); //以它的Pid成为进程祖id
25 printf("\n");
26 printf("parent pid = %d\n", getpid());
27 printf("parent's parent process pid = %d\n",getppid());
28 printf("parent group id = %d\n",getpgid(0));
29 sleep(3);
30 setpgid(getpid(), getppid());//改变父进程的组id为父进程的父进程
31 printf("group of parent is changed to %d\n", getpgid(0));
32
33 while(1);
34 }
35 return 0;
36 }
第八天
mmap函数
mmap()函数将一个文件或者其它对象映射尽内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。
mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
void *mmap(void *start, size_t length, int port, int flags, int fd, off_t offsize);
参数的意义:
Start: 指向欲映射的内存初始地址,通常设为NULL,代表让系统自动选定地址,映射成功后返回该地址。
Length: 代表将文件中的多大的部分映射到内存。
Port: 映射区域的保护方式
PROT_EXEC映射区域可被执行
PROT_READ映射区域可被读取
PROT_WRITE映射区域可被写入
PROT_NONE映射区域不能存取
Flags: 影响映射区域的各种属性。在调用mmap()时必须指定MAP_SHARED或MAP_PRIVATE
MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。
MAP_SHARED 对映射区域的写入数据会复制会文件内,而且允许其它映射文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写时复制”对此区域的任何修改都不会写回原来的文件内容。
MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。
父子进程进行文件通信
1 #include<stdio.h>//父子进程通过文件进行进程间通信
2 #include<stdlib.h>
3 #include<string.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<unistd.h>
7 #include<sys/wait.h>
8 int main()
9 {
10 int fd1, fd2;
11 pid_t pid;
12 char *str = "------test for share fd in parent child process---\n";
13 pid = fork();
14 if(pid < 0)
15 {
16 perror("fork ");
17 exit(1);
18 }
19 else if(pid == 0)
20 {
21 fd1 = open("test.txt", O_RDWR);
22 if(fd1 < 0)
23 {
24 perror("open file ");
25 exit(1);
26 }
27 write(fd1, str, strlen(str));
28 printf("child wrote over...\n");
29 }
30 else
31 {
32 fd2 = open("test.txt",O_RDWR);
33 if(fd2 < 0)
34 {
35 perror("open file");
36 exit(1);
37 }
38 sleep(1);
39 char buf[1024]={
0};
40 int len = read(fd2, buf, sizeof(buf));
41 write(STDOUT_FILENO, buf,len);
42 wait(NULL);
43 }
44 return 0;
45 }
通过mmap向文件中写数据
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<fcntl.h>
5 #include<sys/types.h>
6 #include<unistd.h>
7 #include<sys/mman.h>
8 #include<sys/wait.h>
9 int main()
10 {
11 int fd;
12 char *p = NULL;
13 fd = open("mytest.txt", O_CREAT | O_RDWR ,0644);
14 if(fd < 0)
15 {
16 perror("open ");
17 exit(1);
18 }
19 int len = ftruncate(fd, 4);
20 if(len == -1)
21 {
22 perror("ftruncate ");
23 exit(1);
24 }
25 p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
26 if(p == MAP_FAILED)
27 {
28 perror("mmap ");
29 exit(1);
30 }
31 strcpy(p, "abc");//写数据
32 close(fd);
33 int ret = munmap(p, 4);
34 if(ret == -1)
35 {
36 perror("munmap ");
37 exit(1);
38 }
39 return 0;
40 }
父子进程通过mmap通信
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/mman.h>
5 #include<unistd.h>
6 #include<sys/types.h>
7 #include<fcntl.h>
8 #include<sys/wait.h>
9 int var = 100;
10 int main()
11 {
12 int *p;
13 pid_t pid;
14 int fd;
15 fd = open("temp", O_CREAT | O_RDWR, 0644);
16 if(fd < 0)
17 {
18 perror("open ");
19 exit(1);
20 }
21 unlink("temp");//删除临时文件目录项,使之具备被释放的条件
22 ftruncate(fd, 4);
23 p = mmap(NULL, 4, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
24 //p = mmap(NULL, 4, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);
25 if(p == MAP_FAILED)
26 {
27 perror("mmap ");
28 exit(1);
29 }
30 close(fd);
31 pid = fork();
32 if(pid == 0)
33 {
34 *p = 2000;
35 var = 1000;
36 printf("child, *p = %d,var = %d\n",