Linux_系统编程day01

将近日学习Linux基础编程,系统编程,网络编程的word笔记搬运过来,图片无法直接粘贴,就丢失掉了。

日后工作学习中使用与扩充维护还是word笔记

系统编程day01

内容简介:

程序和进程

         磁盘中的二进制文件,运行的程序,cpu,虚拟地址空间,物理内存

并行和并发

         时间碎片和多核

pcb进程控制块

         文件描述符,进程id

进程三态

         就绪态,执行态,挂起态

虚拟地址空间

         4G32位,内核区,用户区,堆栈全局代码区共享库区

fopen打开的*FILE是什么

         文件描述符表,文件指针,缓存(库函数比系统函数快)

进程控制

         fork,pid_t, getpid,getppid, 读时共享写时复制, 循环创建多个线程

进程相关命令

         psaux, ps ajx, kill -9 pid

exec函数族

         execl,execlp, pathname, 占位符, NULL哨兵

进程回收

         孤儿进程僵尸进程,wait,waitpid(0, -1, -pid, pid), int stat, 父进程死僵尸也死(init)

 

 

 

1 了解进程相关的概念

         程序和进程

                   程序:二进制文件,占用磁盘空间

                  进程:启动的程序

                            所有数据都在内存中

                            需要占用更多的系统资源

                                     cpu,物理内存

2 并行和并发

         并发:

                   是时间段的概念,不是时间点的概念

                   cpu每分钟一杯咖啡,让200个人每人都喝一口咖啡

                   cpu在处理进程时在不同的进程间轮询地切换

         并行:

                   cpu多核,有多个处理器

 

3 pcb进程控制块(结构体)每启动一个进程

         structtask_struct

         进程id,系统中每个进程都有唯一的id,c语言中用pid_t类型表示,就是非负整数

进程三态

         进程有初始态,就绪,运行,挂起,终止等状态

         进程切换时需要保存和恢复的cpu寄存器

       虚拟地址空间

Linux下每启动一个进程,就对应一个虚拟地址空间,里面有运行程序的所有信息,但是运算都在物理内存中。

         描述控制终端的信息。打印都是打印到终端上

         当前工作目录(pwd输出当前工作目录)

         umask掩码

         文件描述符表(最大1024)

         和信号相关的信息(linux独有,windows是事件驱动的)

         用户id和组id

         会话(session,多个进程组)和进程组(子进程和父进程默认在同一个组里)

         进程可以使用的资源上限ulimit–a 文件描述符个数上限等

fopen打开的是什么

fopen建立了一个内存缓存区,将磁盘文件先存入内存

 

 

进程控制

进程的虚拟地址空间

 

fork函数创建子进程

fork函数的返回值

         pid==0说明是子进程(儿子什么都不知道)

         pid>0说明是父进程(爸爸知道儿子的名字)

子进程创建成功后代码的执行位置

         从父进程fork后面的代码位置开始执行,因为代码段也是copy的父进程。

父子进程的执行顺序

         不一定,谁抢到了cpu谁先继续运行,先执行的不一定先打印

如何区分父子进程

         通过fork的返回值pid_t pid

getpid/getppid获取进程ID

         getpid得到当前进程的PID

         getppid得到当前进程父进程的PID

父子进程的全局变量

         fork之后父子进程的数据是一样的

         读是共享,写是复制。一旦该全局变量被修改,那么就会在物理内存中copy出一个新的全局变量来被修改。但是打印他们的地址却是一样的,这是因为父子进程的地址空间都是从0开始的,地址即使同为1048,那也是在不同进程的不同地址空间中的。(不同大街的相同门牌号)

 

./a.out fork了一个子进程,但是shell不知道a.out创建了子进程,shell检测到父进程执行完毕就切换到了前台,此时子进程有可能还没有执行完毕,还在向终端打印。此时输入shell命令,shell就会去解析它。

 

循环创建多个进程

循环创建进程时,if(pid == 0) break; 不让子进程创建子进程。

通过循环因子i判断是第几个进程

for( i = 0 ; i<5; ++i)

{

         pid= fork();

         if(pid == 0 )

         {        //是子进程     

                   break;

         }

}

if( i == 0)

{

         puts(“firstprocess”);

}

打印的顺序不能代表程序执行的先后顺序

 

进程相关命令

shell命令ps
ps aux

         TTY是进程依赖于终端的名字,如果是?就表示不依赖于终端

         依赖于终端的进程一般是与用户交互。

ps aux | grep filename

         |管道,是内核中的缓冲区

         左边写的数据,写到管道中,然后grep命令会从管道中获取数据进程查询再输出到终端

ps ajx 查看的信息更全

         可以看到父进程的pid

         PGID是进程组

         SID是会话ID

 

查找到还是没有查找到aaaaaaaaaaa?这是没查找到

管道的前后两个命令是两个进程在工作(找到的是grep命令本身)

 

kill发信号给某个进程

         34--64是给系统预留的信号

         1--31号信号,不同的操作系统是类似的

         kill-9+pid:9号信号是SIGKILL,无条件杀死一个进程

kill -l命令可以查看信号

 

如果父子进程执行的是不相关的操作,那么代码写在一起,很多余

exec()函数族替换进程的代码段

作用

         让父子进程执行不相干的操作

         能够替换进程地址空间中的代码.txt段

         当前程序中调用其他应用程序

                   首先想到exec()之前要先fork()

         子进程调用exec()中的函数,那么子进程的代码就被挖走了

执行指定目录下的程序

      int execl(constchar *path, const char *arg,…);  记住有个占位符

                   path是要执行的程序的路径(建议是绝对路径)

                   变参:arg要执行的程序需要的参数

                           第一个arg:占位的作用,写什么都行,一般是程序的名字

                            后边的arg:需要的参数

                            参数写完之后:NULL

                            一般执行自己写的程序

                            execl(“/bin/ls”,“这是占位符”, “-lah”, NULL); NULL是哨兵表示参数结束

 

int execlp(const char*file, const char *arg, … ); p是pass的意思

         参数与execl()相同

执行系统自带的应用程序

                   /bin

执行PATH环境变量中能够搜索到的程序

 

如何打印exec()函数的错误记录?通过返回值,如果是-1那么就调用失败。或者直接perror(),exec()失败后errno会被设置。然后退出进程exit(1);

exec()函数族是不需要判断返回值的,直接perror(),然后exit(1).因为函数执行成功那就不会执行下面的语句。

进程回收

孤儿进程

         父进程生成子进程,父进程结束,子进程还活着,就叫孤儿进程

         Linux系统的孤儿会被init进程领养init进程变成了孤儿进程的父亲

         为了释放子进程占用的系统资源

                   子进程结束之后,会自己释放用户区所占用的内存空间

但是无法释放pcb,pcb必须由父进程释放

僵尸进程

         孩子死了,爹还活着,但是父进程没有释放子进程的pcb,子进程就变成了僵尸进程

         僵尸进程不是活着的进程。所以父进程一定要等子进程都死了,自己才能死。

      wait 阻塞函数pid_t wait(int *status);

头文件:sys/wait.hsys/types.h

                   条件:子进程死亡

                   返回值:

                            -1:回收失败,已经没有子进程了

                            >0:是回收的子进程对应的pid

                   参数:

                            判断子进程是如何死的status为变量即可,不需要是指针

                                     正常退出 WIFEXITED(status) !=0 是有返回值的

                                               WEXITSTATUS(status);获取进程退出状态参数exit(参数)/return返回值

                                     被某个信号杀死了WIFSIGNALED(status) != 0 没有返回值,被信号杀死

                                               WTERMSIG(status);获得使程序终止信号的编号

                           

                   调用一次wait函数,只能回收一个子进程,回收多个子进程需要循环,如果返回值为-1,就结束循环。

      waitpid可以设置阻塞非阻塞,可以选择回收哪个子进程              pid_t waitpid (pid_tpid, int *status, int options);
       pid:

                   pid>0回收某个子进程的pid

                   pid == -1回收所有子进程

                            仍然需要循环回收,一次只能回收一个

                            while((wpid = waitpid(-1, &status, XXX) ) != -1 );

                   pid= 0:回收当前进程组的所有的子进程。已经送人的子进程无法回收。

                   pid<0:子进程的pid取负号,就是回收不在同一个进程组的子进程

 

       option

                   0:waitpid阻塞

                   WNOHANG:非阻塞 wait no hang

         返回值:

                   -1:回收失败,没有子进程

                   >0:被回收的子进程的pid

         由于是非阻塞的,可能存在无法回收因为子进程还在运行

                   =0:子进程还在运行,回收失败

如何杀死僵尸进程

子进程在父进程结束之前结束,如果父进程不wait,那一定会成为僵尸进程吗?

子进程结束,父进程还在sleep,那么子进程变成僵尸

子进程结束,父进程从sleep中醒来,如果不wait,子进程还是僵尸

所以,父进程一定要wait子进程,除非父进程也很快就结束了

 

一般僵尸进程很难直接kill掉,不过您可以kill僵尸爸爸。父进程死后,僵尸进程成为”孤儿进程”,过继给1号进程initinit始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。

 

ps -e -o ppid,stat | grep Z | cut -d” ” -f2| xargs kill -9

 

kill -HUP `ps -A -ostat,ppid | grep -e’^[Zz]‘ | awk ’{print $2}’`

 

我将nova-novncproxy stop后再start,僵尸进程即消失,问题解决。

 

另外子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。就是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,而此时,尽管对它的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。

 

在终端中打开多个终端ctrl+shift+t

打开多个终端:ctrl+alt+t

 

段错误:内核使用11号信号杀死段错误应用程序

         1访问了非法内存

         2在不可写的空间进行写操作

         3栈溢出

系统编程day02

学习目标:

1 熟练使用pipe进行父子进程间的通信

2 熟练使用pipe进行兄弟进程间的通信

         pipe只能进行有血缘关系间进程通信

3 熟练使用fifo进行无血缘关系进程间通信(有名管道)

4 熟练掌握mmap函数的使用 (内存映射的方式,把磁盘文件加载到动态加载区)

5 掌握mmap创建匿名映射区的方法 (添加宏)

6 使用mmap进行有血缘关系的进程间通信

7 使用mmap进行无血缘关系的进程间通信

 

进程间通信相关概念

1 什么是IPC

         进程间通信

                   InterProcessCommunication

2 进程间通信常用四种方式

         1管道 – 简单

         2信号 – 系统的,使用复杂,开销小

         3共享映射区 – 有无血缘关系进程间通信都可以

         4本地套接字 – 稳定

         5普通文件等

 

pipe管道(匿名)

1 管道的概念

         在磁盘上没有对应的文件,不占用磁盘空间

         本质:

内核缓冲区

                   伪文件,不在磁盘上

         特点:

                   两部分:

                            读端,写端对应两个文件描述符

                            数据写端流入,读端流出

         操作管道的进程被销毁后,管道自动被释放了

         管道两端默认是阻塞的,对文件描述符的操作,读写就是阻塞非阻塞,是文件描述符对应的文件的行为。

 

2 管道的原理

         内部实现方式:队列

                   环形队列,队头和队尾是相对的

                   特点:先进先出

         缓冲区大小:

                   默认4K

                   大小会根据实际情况适当调整

3 管道的局限性

         队列:

                   数据只能读取一次,不能重复读取

         半双工:(这里的半双工和通信上的还不太一样)

                   单工:遥控器-信号传输方向单向

                   半双工:对讲机-同一时刻数据只能沿一个方向传输

         匿名管道:

                   适用于有血缘关系的进程

4 创建匿名管道int pipe(intfd[2]);

         fd– 传出参数,内核创建了管道,需要返回两个文件描述符给你操作

         fd[0]-读端

         fd[1]-写端

         成功返回0,否则返回-1

5 父子进程使用管道通信

         fork()应该在管道创建之后,这样子进程的fd[]和父进程的fd[]里文件描述符相同

 

         单个进程也可是使用管道完成读写操作

         父子进程间通信是否需要sleep()?不需要,管道默认是阻塞的。

 

父子进程ps aux | grep ”bash“

 

思路:ps aux的数据,不输出到终端,输出到管道;grep从管道中读取数据

         1pipe(fd)创建管道

         2fork()创建子进程

         3关闭不需要的管道端

         1数据重定向:dup2(pipe_fd[0], STDIN_FILENO) ; STDIN_FILENO/STDOUT_FILENO

                   多个文件描述符指向同一个文件

         2execlp使用ps

        

如果不关闭执行读操作的写端,每执行一次,grep操作就变多,因为grep在等待数据的写入,grep阻塞了。不用的管道端一定要关闭。

管道的读写行为

         读操作

                   有数据

                            read(fd);- 正常读,返回读出的字节数

                   无数据

                            1写端全部关闭

                                     read解除阻塞,返回0

                                     相当于读文件读到了尾部

                            2没有全部关闭

                                     read阻塞,直到写端有数据写入,所以不写,一定要关闭

         写操作

                   读端全部关闭

                            管道破裂,进程被终止

                            内核给当前进程发信号SIGPIPE

                   读端没全部关闭

                            缓冲区满了

                                     write阻塞

                            缓冲区没满

                                     write继续写

 

2 查看管道缓冲区大小

命令

ulimit -a

         函数

                   fpathconf,name是宏,表示不同的数据项,返回该数据项的大小

 

如何设置非阻塞

         默认读写两端都阻塞

         设置读端为非阻塞pipe(fd)

                   fcntl-变参函数获取修改文件状态

                            复制文件描述符-dup

                            修改文件属性-open的时候对应的flag属性

                   设置方法:

                            1获取原来的flags

                                     int flags = fcntl(fd[0]), F_GETFL);

                            2设置新的flags

                                     flag |= O_NONBLOCK

                                     fcntl(fd[0], F_SETFL, flags);

fifo有名管道先进先出

1 特点

         有名管道

         在磁盘上有这样一个文件ls –l -> p

         文件大小永远为0,也是一个伪文件

         数据存在内核中,有对应的缓冲区

         半双工的通信方式

2 使用场景

         没有血缘关系的进程间通信

3 创建方式

         命令:

                   mkfifo管道名

         函数:

                   int mkfifo(const char *pathname, mode_tmode);

                   mode是权限,也会|~umask

4 fifo文件可以使用IO函数进行操作

 

5 进程间通信

 fifo文件àmyfifo

         两个不相干的进程A(a.c),B(b.c)

         a.c-- >read

                   intfd = open(“myfifo”, O_RDONLY);

                   read(fd,buf, sizeof(buf) );

                   close(fd);

    b.c -->write

                   intfd1 = open(“myfifo”, O_WDONLY);

                   write(fd1,“hello world”, 11);

                   close(fd1);

         默认的是有阻塞属性的

通过命令行参数传参来实现创建指定名字的fifo文件

access判断文件是否存在

 

内存映射区

mmap创建内存映射

         头文件:sys/mman.h

         作用:将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件

         函数原型:

                   void*mmap(

                            void           addr,         //映射区首地址,传NULL,内核知道

                            size_t       length,     //映射区的大小,4K整数倍

                            int           prot,                   //映射区权限, PORT_READ, PORT_ERITE

                                     //映射区必须有读权限

                            int           flags,        //标志位参数, MAP_SHARED, MAP_PRIVATE

                                     //MAP_SHARED:修改的内存数据同步到磁盘

                                     //MAP_PRIVATE:修改的内存数据不同步到磁盘

                            int              fd,              //文件描述符,映射谁,谁的fd

                                      //通过open获得fd

                            off_t          dffset        //映射时文件指针的偏移量

                                     //必须是4k的整数倍

                            );

                   返回值:

                            调用成功,返回映射区的首地址

                            调用失败,返回MAP_FAILED(void *) (-1)

size_t length映射区的大小实际上都是4k的整数倍,且不能为0,其实就是文件的大小

如何获得文件的大小?lseek函数

int port:   PORT_READ

                   PORT_WRITE

 

mmap的释放 munmap

         intmunmap(void *addr, size_t length);

                   addr– mmap的返回值,映射区的首地址

                   length:映射区的长度

         返回值:

                   释放失败,返回-1

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值