Linux程序设计(Beginning Linux Programming 3rd Edition)读书笔记

第一章 入门

1.1 UNIX、Linux和GNU简介

1.1.1 什么是UNIX

UNIX操作系统最初是由贝尔实验室开发的,现在已经成为一种非常流行的多用户、多任务操作系统。

多用户操作系统具体指什么,我怎么老觉得多用户用起来很不踏实呢?

 

严格来说,UNIX是由开放组织管理的一个商标,它指的是一种遵循特定规范的计算机操作系统。

原来是一个商标,呵呵!

UNIX的源代码属于SCO公司。有许多商用的类UNIX系统,如SCO的Unixware、IBM的AIX、HP的HP-UX和Sun的Soloris,还有一些可以免费获得,如FreeBSD和Linux。如今只有少数系统完全遵守开发组织的规范(只有完全遵守的操作系统才被允许挂上“UNIX”的商标,比如呢?)。

 

2.UNIX哲学

典型的UNIX(包括Linux)程序和系统具有如下特点:

简单性

集中性

可重用性

过滤器

开放的文件格式

灵活性

对这些哲学的理解以后再体会?

 

1.1.2 什么是Linux

可能你已经知道,Linux是一个类UNIX内核的可以自由发布的实现版本,是一个操作系统的底层核心。

......其目的是,保证Linux除包含自由发布的代码外,不会集成任何私有代码。

 

1.1.3 GNU项目和自由软件基金会

没完全明白,暂时略?

 

1.1.4 Linux发行版

著名的Linux发行版有Red Hat Linux、SuseLinux和Debian GNU/Linux,当然还有许多其他的发行版。

 

1.2 Linux程序设计

许多人认为Linux程序设计就是用C语言编程。的确,UNIX最初是用C语言编写的,UNIX的大多数应用程序也是用C语言编写的,但C语言并不是Linux程序员或UNIX程序员的唯一选择。在本书中,我们将介绍其他的选择。

从书上列出的Linux程序员可用的部分编程语言来看,我觉得几乎就没有什么语言不能在Linux上用,呵呵。

 

1.2.1 Linux程序

Linux应用程序表现为两种特殊类型的文件:可执行文件和脚本文件。前者相当于Windows中的.exe文件,后者相当于Windows中的.bat文件。

 

Linux并不要求可执行程序或脚本具有特殊的文件名或扩展名。我们将在第2章讨论的文件系统属性用来指明一个文件是否为可执行的程序。

记住到时关注?

 

登录Linux系统时,我们与一个shell程序(通常时bash)进行交互。

原来终端程序的程序名是bash呀,以前连这都不知道,真丢人。启几个终端,然后通过ps-e查看所有进程,就可以看到启了几个终端就有几个bash进程。我们在终端敲入程序,比如刚才的ps,系统怎么知道在哪儿找呢,原来这和Windows上一样,同样有个PATH变量,呵呵!

怎么查看和修改这个变量呢?

 

1.2.2 C语言编译器

历史上,C语言编译器被简称为cc。

估计是c compiler的缩写!同理,如果是GNU C编译器的话,那么自然就缩写为gcc了。现在知道gcc的来历了吧,呵呵!

 

调用GNU C语言编译器(在Linux中大多数情况下用cc也可以)......

我试了一下,cc还真的可以像gcc一样编译呢!gcc和cc是指向同一个程序吗?

如图所示,cc原来是链接到gcc的,即gcc和cc指向同一个程序。

 

因为直接在终端输入程序名是在PATH路径找,如果PATH路径下刚好有同名程序,执行的可不是当前目录下的,所以,要想执行当前目录下的程序,需要特别指明是当前目录下的程序(例如./a.out)。

 

1.2.3 开发系统导引

对于Linux开发人员来说,了解软件工具和开发资源在系统中存放的位置是很重要的。

我也很认同这句话。

1.应用程序

开发程序所用到的东东几乎都是放在/usr下,包括应用程序(比如gcc等),头文件,库文件等。

打开/usr就明白了。

2.头文件

如何添加头文件搜索目录?

 

在这个地方还学到点东西如下,表示在当前目录下搜索包含EXIT_的所有.h文件。记得扩展一下grep的使用!

$ grep EXIT_ *.h

 

3.库文件

标准系统库文件一般存储在/lib和/usr/lib目录中。C语言编译器(或更确切地说是链接程序)需要知道要搜索哪些库文件,默认情况下,它只搜索标准C语言库。这是从那个计算机速度还很慢而且CPU运行周期还很昂贵的时代遗留下来的问题。仅把库文件放在标准目录中,然后希望编译器找到它是不够的,库文件必须遵循特定的命名规范并且需要在命令行中明确规定。

库文件的名字总是以lib开头,随后的部分指明这是什么库(例如,c代表C语言库,m代表数学库)。文件名的最后部分以.开始,然后给出库文件的类型:

.a代表传统的静态函数库(例如libc.a)

.so代表共享函数库

举例

$ gcc -o fred fred.c /usr/lib/libm.a

$ gcc -o fred fred.c -lm       代笔引用/usr/lib中名为libm.a的函数库。-lm标志的另一个好处是如果有共享库,编译器会自动选择共享库

$ gcc -o fred fred.c -L/usr/openwin/lib -lX11     增加库的搜索目录/usr/openwin/lib

注意L和l是有区别的,前者跟目录,后者跟库名

 

4.静态库

下面是建立静态库的过程:

$gcc -c bill.c fred.c            将源文件编译成bill.o,fred.o目标模块

$gcc -c program.c                在program.c中调用了bill.c中的函数,我们也先编译它

$ar crv libfoo.a bill.ofred.o   将bill.o fred.o添加到静态函数库libfoo.a中

$ranlib libfoo.a                 给libfoo.a建立索引,以便加快链接的速度

$gcc -o program program.olibfoo.a 将program.o和库libfoo.a链接起来生成最终的程序program

或者

$gcc -o program program.o -L. -lfoo

或者

$gcc -o program program.clibfoo.a    编译的同时并链接到libfoo.a

或者

$gcc -o program program.c -L.-lfoo  

 

5.共享库

搜索共享库的其他位置可以在文件/etc/ld.so.conf中配置,如果修改了这个文件,就需要用命令ldconfig来处理。

标准系统库文件一般存储在/lib和/usr/lib目录中。

用ldconfig具体怎么处理?

 

我们可以通过运行工具ldd来查看程序需要的共享库。例如

$ldd program

我试了一下确实还可以,呵呵,注意,是在当前目录查找program程序,如$lddps就会找不到ps程序,不要搞混了,ldd会在标准目录找,但参数ps可不会哦。

 

如何建立动态链接库?如何链接到动态链接库?

 

1.3 获得帮助

比如

man gcc        用来访问在线手册页

info gcc       浏览全部的文档。info系统的优点是,你可以通过链接和交叉引用来浏览文档并可直接跳到相关的章节

 

第2章 shell程序设计

Windows中,命令行是图形界面的补充,但是在Linux中,恰好相反,图形界面是命令行的补充,也就是说命令行能干基本所有的事情,所以在Linux中,shell程序设计是那么的重要。

 

2.1 为什么是用shell编程

......它提供了多种不同的shell程序。在UNIX商业版本中最常见的可能是Kornshell,但还有许多其他的shell。

原来shell还有多种,以前还以为shell都是一个程序呢,呵呵!

 

使用shell进行程序设计的原因之一,你可以快速、简单地完成编程。同时,即使是在最基本的Linux安装中也会提供一个shell。因此,如果你有一个简单的构想,则可以通过它检查自己的想法是否可行。shell也非常适合于编写一些执行相对简单的小工具,因为它们更强调的是易于配置、维护和可移植性,而不是很看重执行的效率。你还可以使用shell对进程控制进行组织,使命令按照预定的顺序在前一阶段命令成功的前提下顺序执行。

 

2.2 一点哲学

现在,我们来关注一点UNIX(当然也是Linux)的哲学。UNIX架构非常依赖于代码的高度重用性。如果你编写了一个小巧而简单的工具,其他人就可以将它作为一根链条上的某个环节来构成一条命令。下面是一个简单的例子:

$ls -al | more

这个命令使用了ls和more工具并通过管道实现了文件列表的分屏显示。每个工具就是一个组成部件。通常你可以将许多小巧的脚本程序组合起来以创建一个庞大而复杂的程序。

 

2.3 什么是shell

shell是一个用户与Linux系统间接口的程序,它允许用户向操作系统输入需要执行的命令。这点与Windows的命令行提示符类似,但正如先前所提到的,Linuxshell的功能更强大。例如我们可以使用<和>堆输入输出进行重定向,使用|在同时执行的程序之间实现数据的管道传递,使用$(...)获取子进程的输出。

 

想要更改到另一个shell--例如,bash不是你的系统中默认的shell,只需要直接执行需要的sehll程序(例如,/bin/bash)就可以运行新的shell并且改变命令提示符。

 

 

 

第11章 进程和信号

 

进程和信号构成了Linux操作环境的基础部分。它们控制着Linux和所有其他类UNIX计算机系统执行的几乎所有活动。

 

每个进程都会被分配一个唯一的数字编号,我们称之为进程标识符或PID。它通常是一个取值范围从2到32768的正整数。当进程被启动时,系统将按顺序选择下一个未被使用的数字作为它的PID,当数字已经回绕一圈时,新的PID重新从2开始。数字1一般是为特殊进程init保留的,init进程负责管理其他进程。

ps -ef

e和A选项是一样的,都是显示所有进程,如果不想看系统进程,则不要加e选项则可。f可以显示进程所属用户等项。l选项好像比f更全,比如还能显示进程的优先级。

 

在许多Linux系统(也包括一些UNIX系统)上,在目录/proc中有一组特殊的文件,这些文件的特殊之处在于它们允许你“窥视”正在运行的进程的内部情况。

学会常用的一些

 

Linux进程表就像一个数据结构,它把当前加载在内存中的所有进程的有关信息保存在一个表中,其中包括进程的PID、进程状态、命令字符串和其他一些ps命令输出的各类信息。

 

操作系统根据进程的nice值来决定它的优先级,一个进程的nice值默认为0并将根据这个程序的表现而不断变化。

nice值的范围从-20到19,优先级越高,所可能获得的CPU时间就越多。我们可以

nice -n 1 ls         // 将 ls 的优先级加 1 并执行

nice ls              // 将 ls 的优先级加 10 并执行

renice 18 3246       //  调整3246进程的优先级为18

 

11.3 启动新进程

我们可以在一个程序的内部启动另一个程序,这个工作可以通过库函数system来完成。

ls &        // &表示在后台执行ls,也就是说,在ls未执行完之前当前进程就可以继续执行了

system函数很有用,但它有局限性,因为程序必须等待由system函数启动的进程结束之后才能继续,除非加&(例system("ls &"))。Windows中没有这个问题

 

一般来说,使用system函数远非是启动进程的理想手段,因为它必须用一个shell来启动需要的程序。由于在启动程序之前需要先启动一个shell,而且对shell的安装情况及使用的环境的依赖也很大,所以使用system函数的效率不高。

 

11.3.1 替换进程映像

exec函数系列由一组相关的函数组成,它们在进程的启动方式和程序参数的表达方式上各有不同。一个exec函数可以把当前进程替换为一个新进程。

例:

#include <stdio.h>

#include <unistd.h>

int main()

{

   execl("/bin/ps","ps","-ef", 0);

   printf("Done\n");

   return0;

}

程序永远不会打印出Done,因为运行中的程序开始执行ps可执行文件中的代码。新进程ps的PID、PPID和nice值与当前程序的完全一样。

一般情况下,exec族函数是不会返回的,除非发生了错误。出现错误时,函数返回-1,并且会设置错误变量errno。

由exec族启动的新进程继承了原进程的许多特征。特别是,在原进程中已打开的文件描述符在新进程中仍将保持打开,除非它们的“执行时关闭标志”被置位。任何在原进程中已打开的目录流都将在新进程中被关闭。

原进程中的正在运行的其他线程会怎样呢?替换进程在什么场合下用呢?

答:

 

11.3.2  复制进程映像

我们可以通过调用fork创建一个新进程。这个系统调用复制当前进程,在进程表中创建一个新的表项,新表项中的许多属性与当前进程是相同的。新进程几乎与原进程一摸一样,执行的代码也完全相同,但新进程有自己的数据空间、环境和文件描述符。

会复制进程中的所有线程吗?

答:

 

11.3.3 等待一个进程

我们可以通过在父进程中调用wait函数让父进程等待子进程的结束。

The wait() system call suspendsexcution of the calling process until one of its children terminates.The callwait(&status) is equivalent to :

waitpid(-1, &status, 0);

wait(&status)和Windows中比较起来,有点怪怪的,它并不需要被等待的进程id,只要任何一个子进程退出,它都结束等待。

 

11.3.4 僵尸进程

相当于Windows中的某个进程虽然结束了,但是还有别的进程打开着它的进程句柄。

子进程终止时,它与父进程之间的关联还会保持,直到父进程也正常终止或父进程调用wait才告结束。(跟CreateProcess一样,需要显式关闭子进程句柄才能断开关系)

 

如果此时父进程异常终止,子进程将自动地把PID为1的进程init作为自己的父进程。

 

11.3.6 线程

在Linux(或UNIX)系统中编写线程程序并不像编写多进程程序那么常见,因为Linux中的进程都是非常轻量级的,而且编写多个互相协作的进程比编写线程要容易得多。

 

11.4 信号

信号是由于某些错误条件而生成的,如内存段冲突、浮点处理器错误或非法指令等。它们由shell和终端管理器生成以引起中断,它们还可以作为在进程间传递消息或修改行为的一种方式,明确地由一个进程发送给另一个进程。无论何种情况,它们的编程接口都是相同的。信号可以被生成、捕获、响应或(至少对于一些信号)忽略。

信号的名称是在头文件signal.h中定义的。它们以SIG开头。

SIGABORT    进程异常终止

......

如果进程接收到这些信号中的一个,但事先没有安排捕获它,进程将会立刻终止。通常,系统将生成核心转储文件core,并将其放在当前目录下。该文件是进程在内存中的映像,它对程序的调试很有用处。

还没试过呢?

答:以验证通过,不要忘了ulimit -c unlimited,否则产生不了core文件。

 

SIGCHLD    子进程已经停止或退出

......

SIGCHLD信号对于管理子进程很有用。它是默认被忽略的。其余信号会使接收它们的进程停止运行,但SIGCONT是个例外,它的作用是让进程恢复并继续执行。shell脚本通过它来对作业进行控制,但用户很少会用到它。

CTRL + C 对应 SIGINT

如果想要发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用kill命令。

kill -HUP 512      // 向512进程发送“挂断”信号

 

#include <signal.h>

#include <stdio.h>

#include <unistd.h>

 

void ouch(int sig)

{

   printf("OUCH!- I got signal %d\n", sig);

   (void)signal(SIGINT,SIG_DFL);  //表示如果收到SIGINT信号就交由默认处理

}

 

int main()

{

   (void)signal(SIGINT,ouch);    //表示如果收到SIGINT信号就交由ouch处理

 

   while(1)

   {

      printf("HelloWorld!\n");

      sleep(1);

   }

}

是不是信号处理完后,从当前中断的地方继续处理?

答:

 

在信号处理程序中,调用如printf这样的函数是不安全的。一个有用的技巧是,在信号处理程序中设置一个标志,然后在主程序中检查该标志,如需要就打印一条消息。在本章的结尾部分,你将会看到一个函数列表,表中的函数都可以在信号处理程序中被安全调用。

为什么不安全?

 

我们不推荐大家使用signal接口。之所以在这里介绍它,是因为你可能在许多老程序中看到它的应用。稍后我们会介绍一个定义更清晰、执行更可靠的函数sigaction,在所有的新程序中都应该使用这个函数。

 

11.4.1 发送信号

进程可以通过调用kill函数向包括它本身在内的其他进程发送一个信号。如果程序没有发送该信号的权限,对kill函数的调用就将失败。

int kill(pid_t pid, int sig);

 

进程可以通过调用alarm函数在经过预定时间后发送一个SIGALRM信号。

unsigned int alarm(unsigned intseconds);

 

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

什么信号都可以还是说只能是SIGALRM?

答:试了一下,怎么直接就把进程杀掉了

 

sigcation用法太多了,掌握一些典型用法就行:

当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,类似下面的代码:

     #include <signal.h>

     ……

     void sig_handler_with_arg(intsig,siginfo_t *sig_info,void *unused){……}

   

     int main(int argc,char **argv)

     {

              struct sigaction sig_act;

              ……

             sigemptyset(&sig_act.sa_mask);

             sig_act.sa_sigaction=sig_handler_with_arg;

              sig_act.sa_flags=SA_SIGINFO;

 

               ……

     }

        如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction,你的程序可能类似下面的代码:

     #include <signal.h>

     ……

     void sig_handler(int sig){……}

   

     int main(int argc,char **argv)

     {

              struct sigaction sig_act;

              ……

              sigemptyset(&sig_act.sa_mask);

              sig_act.sa_handler=sig_handler;

              sig_act.sa_flags=0;

 

               ……

      }

 

11.4.3 信号集

头文件signal.h定义了类型sigset_t和用来处理信号集的函数。sigaction和其他函数将用这些信号集来修改进程在接收到信号时的行为。

sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

被阻塞是指在信号处理过程中不再接收其他的信号吗?

答:经验证,是这样的。

比如sa_mask中的信号集为空,那么当进程正在处理SIGINT时,再次发出SIGQUIT,会导致进程退出。而当sa_mask中包含SIGQUIT信号时,再次发出SIGQUIT不会导致进程退出,程序仍然继续处理它的SIGINT信号。

 

常用信号参考

SIGALRM SIGHUP SIGINT SIGKILLSIGPIPE SIGTERM SIGUSR1 SIGUSR2

这些信号的默认动作都是异常终止进程,进程将以_exit调用方式退出。

 

SIGFPE SIGILL SIGQUIT SIGSEGV

这些信号也会引起进程的异常终止,但可能还会有一些与具体实现相关的额外动作,比如创建core文件等。

 

SIGSTOP SIGTSTP SIGTTIN SIGTTOU

这些信号会引起进程被挂起。

 

SIGCONT SIGCHLD

SIGCONT信号的作用是重启被暂停的程序。如果进程没有暂停,则忽略该信号。SIGCHLD在默认情况下被忽略。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值