Linux程序设计 Beginning_Linux_Programming

£可爱£侵袭症+ 提交于 2019-12-28 00:01:25

 好文:https://blog.csdn.net/qq_29996285/article/details/94191750

2019-06-17

21:48:55

 

终于知道为啥C语言开头要include<stdio.h> 了!

 

Write系统调用:

 

 

 OPEN系统调用:

 

 

 

Close 系统调用:

 

 ioctl系统调用:

 

一个文件复制程序:

 

 

 

 

改进版:

 

一个目录扫描程序:P103

改进main函数,使其变成一个更有用的目录浏览器。

 输出结果将分页显示,用户可以通过翻页查看其输出。

可以说,用户现在有了一个非常方便,通用的目录树浏览器。

 

 

 3.10 /proc文件系统

该目录中包含了许多特殊文件用来对驱动程序和内核信息进行更高层的访问

查看CPU:

查看内存:

查看内核版本信息:

查看网络套接字的使用统计

 查看系统中所有运行的程序同时能打开的文件总数:

 

 ps命令会给出当前正在运行进程的列表。


第四章: Linux环境:

4.1程序参数

这个程序利用计数参数argc建立一个循环来检查所有的程序参数。

 

 

 4.2 环境变量

4.2.2 environ变量。

程序可以通过environ变量直接访问这个字符串数组。

这个程序遍历environ遍历,并打印出整个环境。

 

 4.3 时间和日期

这个程序演示了time函数的用法

以从1970年开始计算的秒数来表示时间和日期。

 

 为了提供(对人类)更有意义的时间和日期,你需要把时间值转换为可读的时间和日期。有一些标准函数可以帮我们做到这一点。

gmtime函数把底层时间值分解为一个结构,该结构包含一些常用成员。(见书)

注意,int tm_year表示的是从1900年开始计算的年份

 为了得到更“友好"的时间和日期表示,像date命令输出的那样,你可以使用asctime函数和ctime函数:

ctime函数等效于调用下面这个函数:
asctime(localtime(timeval))

它以原始时间值为参数,并将它转换为一个更易读的本地时间。

 

 4.4 临时文件

4.5用户信息

 

4.6主机信息

 你可以通过uname系统调用获得关于主机的更多详细信息。

 4.7 日志

4.8资源和限制

一个程序耗费的CPU时间可分为用户时间和系统时间


 

第五章 终端

 5.1对终端进行读写

第六章:使用curses函数库管理基于文本的屏幕

第7章 数据管理

 7.1内存管理

简单的内存分配:

这表明malloc确实返回了1MB的可用内存

 

请求全部的物理内存:8G

 可用内存:

最终,当应用程序耗尽所有的物理内存和交换空间,或者当最大栈长度被超过时,内核将拒绝此后的内存请求,并可能提前终止程序的运行。

这种“终止进程”实际上是为了既能让进程快速高效地分配内存,又能让Linux内核保护自己免受资源耗尽的破坏而做的一个很好的妥协。

 

滥用内存:

为了确保一个行为恶劣的程序(如本例)无法破坏任何其他程序,Linux会终止其运行。

 

访问空指针:

释放内存:

7.2 文件锁定

 第8章:Mysql(看不太懂)

第9章 开发工具(同上)

第10章 调试

第11章 进程和信号

查看进程

查看进程的状态:

查看进程的优先级:(对应NI这一栏)

nice 可以提高优先级

 


 

Makefile 效果图:

要知道 gcc -c 和 gcc -o 的区别

和编写makefile 的前后顺序(有什么依赖关系) 和 make 后的执行顺序要做个对比

 

牛逼:程序来自《C语言程序设计现代方法》

 


11.3 启动新进程

system系统调用(效率不高)

shell最重要的两个系统调用:exec和fork。这两个系统调用的组合威力巨大!exec是替换进程映像,而fork是复制进程映像!

替换进程映像:

exec系统调用。exex函数可以把当前进程替换为一个新进程。

实验:execlp函数:

 

实验解析:见书

 复制进程映像:

 

 

 这个程序以两个进程的形式在运行。子进程被创建并且输出消息5次。原进程(即父进程)只输出消息3次。父进程在子进程打印完它的全部消息之前就结束了,因此我们将看到在输出内容中混杂着一个shell提示符。程序在调用fork时被分为两个独立的进程。程序通过fork调用返回的非0值确定父进程,并根据该值来设置消息的输出次数,两次消息的输出之间间隔1秒。

 等待一个进程:

wait系统调用:wait 系统调用将暂停父进程直到它的子进程结束为止。这个调用返回子进程的PID,它通常时已经结束运行的子进程的PID。状态信息允许父进程了解子进程的退出状态,即子进程的main函数返回的值或子进程中exit函数的退出码。

状态信息码在sys/wait.h文件的宏里!

在上一个程序中改进

 

 僵尸进程:

 子进程终止时,它于父进程之间的关联还会继续保持,直到父进程也正常终止或父进程调用wait才告结束。因此,进程表中代表子进程的表项不会立刻释放。虽然子进程已经不再运行,但它仍然存在于系统中,因为它的退出码还需要保存起来,以备父进程今后的wait调用使用。这时它将成为一个僵尸进程

改动了这里:

如果此时父进程异常终止,子进程将自动把PID为1的进程(即init)作为自己的父进程。子进程现在是一个不再运行的僵尸进程,但因为其父进程异常终止,所以它由init进程接管。僵尸进程将一直保留在进程表中直到被init进程发现并释放。

进程表越大,这一过程就越慢。应该尽量避免产生僵尸进程,因为在init清理它们之前,它们将一直消耗系统的资源。

输入和输出重定向:

它读取输入并将输入字符转换为大写

 

 

可以利用shell的重定向把一个文件的内容全部转换为大写: iiiiiiiiiiiiiiiiiiii

11.4 信号

接收到信号的进程会采取一些行动

信号是由某些错误条件而生成的,它们由shell和终端处理器生成来引起中断。

信号的名称是在头文件signal.h中定义的。

现在,我们只需要知道如果shell和终端驱动程序是按通常情况配置的话,在键盘上敲入中断字符(通常是Ctrl+C组合键)就会向前台进程发送SIGNINT(终端中断)信号,这将引起该程序的终止,除非它实现安排了捕获这个信号。

如果想发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill 命令。该命令需要有一个可选的信号代码或信号名称和一个接受信号的目标进程的PID,(用ps 查看)

例如想要终止进程PID为512的进程 —— kill -HUP 512

第一次按下Ctrl + C 时会让程序作出响应,然后程序继续执行,再次按下Ctrl+C时,程序将结束运行。因为SIGNINT信号的处理方式已恢复为默认行为——终止程序的运行。

 

 程序:模拟一个闹钟

这个程序用到了一个新的函数pause,它的作用很简单,就是把程序的执行挂起直到有1个信号出现为止。

 一个健壮的信号接口:sigaction

这个程序用sigaction代替signal来设置ctrl+c 的信号处理函数为ouch.它首先必须设置一个sigaction结构,在该结构中包含信号处理函数,信号屏蔽字和标志。

 


 

12章:POSIX 线程

 线程的由来:人们认为用fork调用来创建新进程的代价太高。在这种情况下,如果能让一个进程同时做两件事情或至少看起来是这样将会非常有用。

12.1 什么是线程

事实上,所有的进程都至少有一个执行线程。弄清楚fork系统调用和创建新线程之间的区别非常重要。

当进程执行fork调用时,将创建出该进程的一份新副本。这个新进程拥有自己的变量和自己的PID,它的时间调度也是独立的。它的执行几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量),但与它的创建者共享全局变量,文件描述符,信号处理函数和当前目录状态。(超级重要!)

有关POSIX标准的线程历史可以参考书上。

其中对线程的改进,大部分工作都集中在如何将用户级线程映射到内核级线程。

 12.2线程的优点和缺点

(详细见书)

优点:

新线程的创建代价要比新进程小的多

一般而言,线程之间的切换需要操作系统做的工作要比进程之间的切换少得多,因此多个线程对资源的需求要远小于多个进程。

缺点:

编写多线程程序需要非常仔细的设计。

对多线程程序的调试比对单线程程序的调试困难得多。

12.3 第一个线程程序

函数:pthread_create :它的作用是创建一个新线程,类似于创建新进程的fork函数。(具体参数见书)

fork调用后,父子进程将在同一位置继续执行下去,只是fork调用的返回值是不同的;但是对新线程来说,我们必须明确地提供给它一个函数指针,新线程将在这个新位置开始执行。

pthread_exit 等同于进程中的exit()函数

phread_join等同于进程中的wait函数

最简单的线程程序

简单解析:

如果调用phread_create成功后,原先的线程继续执行后面的代码,而新线程开始执行thread_function函数

原先的线程在查明新线程已经启动后,将调用pthread_join函数

给该函数传递两个参数,一个数正在等待其结束的线程的标识符,另一个是指向线程返回值的指针。这个函数将等到它所指向的线程终止后才返回。然后主线程将打印新线程的返回值和全局变量message的值,最后退出。

可以看到:新线程修改了数组message,而原先的线程也可以访问该数组。如果我们调用的是fork而不是pthread_create,就不会有这样的效果。

 12.4同时执行

引入信号量的前身。

在这个程序中我们是在两个线程之间使用轮询技术。

通过改变变量的值来充当信号量的作用。

12.5 同步

上一节中,我们采用的在它们之间进行切换的方法是非常笨拙且没有效率的。幸运的是,专门有一组设计好的函数为我们提供了更好的控制线程执行和访问代码临界区的方法。

信号量主要分为两种:二进制信号量和计数信号量。这里主要讨论二进制信号量。

信号量是一个特殊类型的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。

 12.5.1用信号量进行同步

包含头文件:#include <semaphore.h>

信号量通过sem_init函数创建。

sem_wait函数相当于p操作

sem_post函数相当于v操作。

sem_destory的作用:用完信号量后对它进行清理。

和大多数Linux函数一样,这些函数在成功时都返回0

 

 设置信号量的同时,我们等待着键盘的输入。当输入到达时,我们释放信号量,允许第二个线程在第一个线程再次读取键盘输入之前统计出输入字符的个数。

这两个线程共享同一个work_area数组。

 

改动这里:

 这里出现了问题。问题在于:我们的程序依赖其接受文本输入的时间要足够长,这样另一个线程才有时间在主线程还未准备好给它更多的单词取统计字符的个数。

这个程序中,第二个线程去执行,但信号量已被增加了不止一次,所以字符统计线程就会反复统计字符数目并减少信号量的值,直到它再次变为0为止。

这个例子显示:在多线程程序中,我们需要对时序考虑的非常仔细。为了解决上面程序中的问题,可以再增加一个信号量,让主线程等到统计线程完成字符个数的统计后再继续执行。但更简单的方法是使用互斥量。

12.5.2用互斥量进行同步

用于互斥量的基本函数和用于信号量的函数非常相似,它们的定义如下:

函数:

 int phreade_mutex_init

 int phread_mutex_lock

int phread_mutex_unlock

int phread_mutex_destroy

 12.6 线程的属性

detachedstate:这个属性允许我们无需对线程进行重新合并

schedpolicy:这个属性控制线程的调度方式。

12.7 取消一个线程

12.8 多线程

第13章 进程间通信:管道

 

第14章:信号量,共享内存和消息队列

在本章中,我们将讨论一组进程间通信的机制,它们又被常称为IPC(进程间通信)机制

信号量:用于管理对资源的访问

共享内存:用于在程序之间高效地共享数据

消息队列:在程序之间传递数据的一种简单方法。

 

更普适性:PV操作

注意,只用一个普通变量进行类似的加减法是不行的,因为在C,C++或几乎任何一个传统的编程语言中,都没有一个原子操作可以满足检测变量是否为true,如果是再将该变量设置为false的需要。这也是信号量操作如此特殊的原因。

 Linux的信号量机制:

Linux系统中的信号量接口经过了精心的设计,它提供了比通常所需要更多的机制。所有的Linux信号量函数都是针对成组的通用信号量进行操作,而不是只针对一个二进制信号量。

乍看起来,这好像把事情弄得更复杂了,但在一个进程需要锁定多个资源的复杂情况中,这种能够对一组信号量进行操作的能力是一个巨大的优势。

信号量函数的定义如下所示:

 

 

共享内存:

典型应用:生产者——消费者问题

它允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。

大多数共享内存的具体实现,都把由不同进程之间共享的内存安排为同一段物理内存。

共享内存是由IPC为进程创建的一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到他们自己的地址空间中。所有的进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。

 

实际情况要比图中显示的更加复杂,因为可用内存实际上由物理内存和已交换到磁盘上的内存页面混合组成。

(因为无法识别 "shm.h" ,所以程序运行不了)

消息队列:

消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的一些问题,比如管道满时的阻塞问题。

消息队列提供了一种在两个不相干的进程之间传递数据的相当简单且有效的方法。与命名管道相比,消息队列的优势在于,它独立于发送和接受进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。

IPC状态命令:

大多数Linux系统都提供了一组命令,用于从命令行上访问IPC消息以及清理游离的IPC机制。它们是ipcs和ipcrm命令,这两个命令对于开发程序非常有用。

状态命令(ipcs)和删除命令(ipcrm)提供了一种检查和清理IPC机制的方法。

显示信号量状态:

ipcs -s

显示共享内存状态:

ipcs -m

显示消息队列状态:

ipcs -q


 第15章 套接字

 

 

 

 

 

 

 

 访问权限前面的字母s和这一行末尾的等号= 表示该设备的类型是“套接字”.

 如果使用ps命令,你可以看到服务器正运行在后台。它目前处于休眠状态,因此没有消耗CPU资源

 

 

 

 15.2.1 套接字属性(与套接字有关的系统调用)

当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址。

 

流:TCP

数据包:UDP

 

在本章中,我们将重点讨论UNIX网络套接字和文件系统套接字,它们不需要你选择一个特定的协议,只需要使用其默认值即可。

 

bind调用需要将一个特定的地址结构指针转换为指向通用地址类型(struct sockaddr *)

 

我们可以用netstat命令查看网络连接状况。

 

 

 

 

 

15.4 多客户

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!