《读书笔记》系列1:UNIX环境高级编程

第七章 进程环境

Int main(int argc,char *argv[]);

其中argc是命令行的数目,argv是指向参数的各个指针所构成的数组。当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序将此启动例程指定为程序的起始地址—这是有链接器设置的,链接器由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数。
进程终止有8种方式,正常终止的有:从main返回、调用exit、。。。、线程调用pthread_exit,异常终止的有:调用abort、接到一个信号并终止。
内核使程序执行的唯一方法是调用一个exec函数,进程资源终止的唯一方法是显示或隐式地调用_exit或_Exit。进程也可非资源地由一个信号使其终止。

C程序的存储空间由下面几部分组成:
1. 正文段(代码段):正文段是可共享的,正文段是只读的,以防止程序被意外修改。
2. 初始化数据段。
3. 未初始化数据段,也称为bss段。
4. 栈:自动变量及每次函数调用时所需保存的信息放在此段。
5. 堆:动态存储分配的数据放在此。

Char*getenv(const char*name);获取环境变量
//setenv设置环境变量

第8章 进程控制

每个进程都有一个非负整数的唯一进程ID。当一个进程终止后,unix系统实现延时重用算法,使得赋予新建进程的ID不同与最近终止进程所使用的ID,防止将新进程误认为是先前已终止的进程。ID为0的进程通常是调度进程,ID为1的通常是init进程。Init进程绝不会终止,它是一个不同的用户进程,但是以超级用户特权执行。

一个现有进程可以调用fork函数创建一个新进程。Pid_tfork(void);子进程是父进程的副本,子进程获得父进程数据空间、堆和栈的副本。

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。父进程可以选择忽略改信号,或者提供一个该信号发生时即被调用执行的函数。

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了竞争条件。为了避免竞争条件和轮询,在多个进程之间需要有某种形式的信号发送和接收的方法。在UNIX中可以使用信号机制。各种形式的进程间通信(IPC)也可使用。

当进程调用一种exec函数时,该进程执行的程序完全低缓为新程序,而新程序则从mian函数开始执行。因为调用exec并不创建进程,所以前台的进程ID并为改变。Exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

在UNIX系统中,当程序需要增加特权或需要访问当前并不允许访问的资源是,我们需要更换自己的用户ID和组ID,使得新ID具有合适的特权或访问权限。可以用setuid函数设置实际用户ID和有效用户ID。一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置用户ID。对于一个特权用户则可将有效用户ID设置为uid。

解释器文件#!。这种文件是文本文件。

System函数在其实现中调用了fork\exec\waitpid,因此有3中返回值。

System函数的一种实现:

#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
Int system(constchar *cmdstring)
{
Pid_t pid;
Int status;
If (cmdstring==NULL)
       Return (1);
If((pid=fork())<0)
{
Status = -1;
}
Else if(pid==0)
{
Execl(“/bin/sh”,”sh”,”-c”,cmdstring,(char*)0);
_exit(127);
}
Else
{
While(waitpid(pid,&status,0)<0){
If(errno!=EINTR){
Status =-1;
Break;
}
}
}
Return (status);
}

使用system而不是使用fork和exec的优点:system进行了所需的各种出错处理以及各种信号处理。

大多数UNIX都有进程会计(processaccounting),启用该选项后,每当进程结束时内核就写一个会计记录。使用二进制数据,记录命令名、所用CPU时间总量、用户ID和组ID、启动时间等。

UNIX系统历史上对进程提供的只是基于调度优先级的粗粒度的控制。调度策略和调度优先级是有内核确定的。进程可以通过调整nice值选择以更低优先级运行。只有特权进程允许提高调度权限。

第9章

如果用户登录正确,login就将完成如下工作。

将当前工作目录更改为改用户的起始目录,调用chown更改该终端的所有权,用login得到的所有信息初始化环境:起始目录,shell,用户名以及一个系统默认路径。

一个父进程已终止的进程称为孤儿进程,这种进程由init进程收养。

第10章 信号

信号使软件中断,很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法。

每个信号都有一个名字,这些名字都已3个字符SIG开头。

信号是异步事件的经典实例,进程不能简单地测试一个变量(如errno)来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,执行以下操作”。

在某个信号出现时,可以告诉内核按下列3中方式处理:(1)忽略此信号,但是SIGKILL和SIGSTOP这两个信号不能忽略。(2)捕捉信号。通知内核在信号发生时,调用一个用户函数。(3)执行系统默认动作。大多数的系统默认动作是终止进程。大多数UNIX系统调式程序都使用core文件检查进程终止时的状态。

在某些例子中,返回给正常调用者的信息可能会被返回给信号处理程序的信息覆盖。信号处理程序中要保证调用安全的函数。这些函数是个重入的并被称为是异步信号安全的。除了可重入意外,在信号处理操作期间,它会阻塞任何会引起不一致的信号发送。

Kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。

使用alarm函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

Nanosleep函数与sleep函数类似,但提供了纳秒级的精度。

第11章 线程

有了多个控制线程以后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个处理各自独立的任务。

多线程编程模型有以下好处:
1. 通过为每种实践类型分配单独的处理线程,可以简化处理异步事件的代码。
2. 多个进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享,而多个线程自动地可以访问相同的存储地址空间和文件描述符。
3. 优先问题可以分解从而提高整个程序的吞吐量。
4. 交互的程序同样可以通过使用多线程来改善响应时间。

每个线程都包含有表示执行环境所必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变狼以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码、程序的全局内存和对内存、栈以及文件描述符。

如果进程中的任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止。

单个线程可以通过三种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
(1) 线程可以简单地从启动例程中返回,返回值是线程的退出码。
(2) 线程可以被同一个进程中的其他线程取消。
(3) 线程调用pthread_exit。

当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保他们在访问变量的存储内容是不会访问到无效的值。(共享、可修改的变量需要进行同步);
可以使用pthread的互斥接口来保护数据,确保统一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放互斥量。

死锁的原因:
可以通过控制互斥量加锁的顺序来避免死锁的发生。
读写锁与互斥量类似,不过读写锁允许更高的并行性。读写锁可以有3中状态:度模式下加锁状态,写模式下加锁状态,不加锁状态。虽然个操作系统对读写锁的实现各不相同,担当读写锁处于读模式锁住的状态,而这时有一个进程视图以邪魔石获取所示,读写所通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的邪魔石锁清秋一直得不到满足。读写所非常适合于对数据结构读的次数远大于写的情况。读写锁也叫做共享互斥锁。
屏障、自旋锁。。。UNIX有很多机制实现线程同步。

第12章 线程控制

第13章 守护进程

守护进程是生存期长的一种进程。他们常常在系统引导装入时启动,仅在系统关闭时才终止。

守护进程的编程规则:(1)调用umask将文件模式创建屏蔽字设置为一个已知值。(2)调用fork,然后使父进程exit。(3)调用setsid创建一个新会话。(4)将当前工作目录更改为根目录。(5)关闭不在需要的文件描述符。(6)。。。

Syslog相关。有以下3种产生日志消息的方法:

内核例程可以调用log函数。(2)大多数用户进程(守护进程)调用syslog(3)函数来产生日志消息。(3)无论一个用户进程是在此主机上,还是在通过TCP/IP网路连接到此主机的其他主机上,都课将日志消息发向UDP端口514。

第14章 高级I/O

低速系统调用是可能会使进程永远拥塞的一类系统调用。非阻塞I/O是我们可以发出open、read和write这样的I/O操作,并使得这些操作不会永远阻塞。如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。

有一种技术称为异步I/O,利用这用技术,进程告诉内核:当描述符准备好可以进行I/O时,用一个信号通知它。

一种比较好的技术是使用I/O多路转接。为了使用这种技术,先构造一张我们感兴趣的描述符的列表,然后调用一个函数,知道这些描述符中的一个已准备好进行I/O时,该函数才返回。Poll\pselect和select这3个函数使我们能够执行I/O多路转接。

第15章 进程间通信

pipe管道是UNIX系统IPC的最古老形式,所有UNIX系统都提供此中通信方式。管道有局限性(半双工的、只能在具有公共祖先的两个进程间使用)。每当在管道中键入一个命令序列,让shell执行时,shell都会为每个命令单独创建一个进程,然后用管道讲前一条命令进程的标准输出与后一条命令的标准输入相连接。

FIFO有时被称为命名管道。通过FIFO,不相关的进程也能交换数据。

猜你喜欢

转载自blog.csdn.net/qguanri/article/details/50053449
今日推荐