Linux系统编程

本文详细介绍了Linux系统编程中的核心概念,包括程序与进程的区别、CPU功能单元、进程控制块PCB、环境变量、并发与进程通信。深入探讨了进程间的通信机制,如管道、mmap,以及信号的处理和同步。讲解了线程的创建、同步原语,如互斥量、读写锁和条件变量,以及信号量在进程间同步的应用。此外,还涵盖了线程的使用注意事项和守护进程的创建。
摘要由CSDN通过智能技术生成

第六天

程序和进程

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、设置修改内存访问级别

CPU
MMU

进程控制块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",
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值