Linux系统编程



课程目标:

  构建一个基于主机系统的多客户即时通信/聊天室项目

涉及的理论知识

进程控制:僵尸进程/孤儿进程、进程控制、守护进程。。。

进程间通信:管道、命名管道、信号。。。

多线程编程: 锁、信号量。。。


参考教程

Robert Love, Linux System program



进程结构

进程由程序、数据和进程控制三部分组成

进程状态

TASK_RUNNING(运行): R 可执行状态。正在执行,在就绪队列中等待。

TASK_INTERRUPTIBLE(可中断): S 睡眠(阻塞)。如果条件满足,内核将其状态设置为运行。收到信号而被提前唤醒并投入运行。

TASK_UNINTERRUPTIBLE(不可中断): D 同可中断状态,但不会因为接收到信号而被唤醒

TASK_ZOMBIE(僵死):  Z 该进程已经结束,但其父进程尚未调用wait(),子进程的进程描述符仍然被保留着。

TASK_STOPPED(停止):  T 停止执行。这种状态发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。

进程状态的查看

ps:显示瞬间进程的状态

常用参数:

l: 长格式输出

u: 按用户名和启动时间的顺序来显示进程

j: 用任务格式来显示进程

f: 用树形格式来显示进程

a: 显示所有用户的所有进程

x: 显示无控制终端的进程

r: 显示运行中的进程

ww: 避免详细参数被截断

$ps            //列出当前shell里当前用户的进程

$ps –u yuhong  //列出用户yuhong运行的所有进程

$ps –el       //以详细列表方式显示运行的所有进程

$ps aux        //以详细的BSD风格显示运行的所有进程

  %MEM:占用的内存的使用率

  VSZ :   虚拟内存大小,即一个程序完全驻留在内存的话需要占用多少内存空间

  RSS:     当前实际占用了多少内存

  STAT:    进程当前状态(R/S/D/Z/T)

       后缀:

        < (高优先级进程)

           N (低优先级进程)

           L (内存锁页)

           s (该进程为会话首进程)

           + (前台进程)

           l (多线程进程)

进程创建与终止

1、进程的创建

  创建函数: pid_t fork(void); (在父进程返回,fork()返回子进程ID,在子进程中返回,fork()返回0。当进程数达到上限或者内存不足时,可能会出错,返回值为-1,系统调用并不直接返回错误码,而是将错误码放在全局变量errno中)

  各种错误情况下errno的值:  1) 进程达到上限 errno=EAGAIN

                2) 系统内存不足 errno=ENOMEM

  查看errno数值的意义

    errno.h

    man 3 errno

  获取进程ID:getpid(); getppid();

  应该避免产生“孤儿进程”(孤儿进程还未结束,父进程却已经结束),解决方法:子进程托孤,或者让其父进程最后退出。

  子进程托孤:init进程(PID=1)接管。

  

  Questions:

  如何实现子进程托孤?fork()例3中的子进程为何能够在父进程退出后,托孤给init进程(难道父进程退出后,自动托孤,不用额外的操作)?

  fork()例3中为什么原进程会存在一个父进程?

  子进程都继承了父进程哪些东西?试用代码举例。

  

2、Linux中的两个特殊的进程

  0号进程:所有进程的祖先

    swapper进程(调度进程):负责进程间的调度,内核直接控制,用户进程无法访问。

    执行cpu_idle()函数

    没有其他进程处于TASK_RUNNING,内核会选择0号进程运行

  0号进程创建的1号进程

    初始化进程在内核引导流程结束时被调用,用于初始化系统环境。初始化文件是/erc/rc*文件、/etc/inittab文件及/etc/init.d目录下的文件。初始化进程从不退出。

    init进程创建和监控其他进程的活动

    接管孤儿进程

3、进程的终止

  1)显式的系统调用

#include <stdlib.h>
void exit(int status);    //退出前把文件缓冲区的内容写回文件

#include <unistd.h>
void _exit(int status);    //退出后缓冲区数据丢失

  这两个函数调用后,进程转化为僵尸进程。

    

  2)从程序结尾离开

  3)被信号终止 SIGTERM(signal terminate) SIGKILL 

    kill [-s <信号名称或编号>][程序]

     kill [-l <信号编号>]
         若不加<信息编号>选项,则-l参数会列出全部的信息名称。

    //强行中止(杀掉)一个进程pid为324的进程:
    #kill -9 324

    #free

  Questions:

    进程管理中信号有哪些,以及编号都是什么,如何使用?

  4)被内核杀掉 Segmentation violation

    当进程出现异常时,会被内核杀掉。

  进程终止内核会传送一个SIGCHLD(signal child)信号给它的父进程

  若一个子进程在终止时整体消失,父进程将无法取回任何的信息

  若子进程先于它的父进程结束,则内核应该让子进程进入僵尸进程的状态,等待父进程来打听它的状态,状态打听后,僵尸进程才会正式结束。

  僵尸进程的内核数据结构

    僵尸进程只会保留最小的骨架:进程的PID,退出状态,运行时间

    僵尸进程的避免:

    i 父进程通过wait和waitpid等函数等待子进程结束(导致父进程立刻阻塞自己,直到有一个子进程退出)。

     #include <sys/types.h>

     #include <sys/wait.h>

       pid_t  wait(int *status);   wait(&status) =>waitpid(-1,&status,0)

     返回值:1.结束的子进程pid      2.-1,如果没有子进程

     status(两个字节):1.高字节:子进程exit时设置的代码,低字节为0    2.如果子进程的退出是因为收到信号,低字节为信号的编码

     有时会见到wait函数的参数是NULL,表示父进程并不关心子进程的状态,只是等待子进程结束,并获得子进程信息,防止其成为孤儿进程或僵死进程。

     pid_t  waitpid(pid_t pid, int *status, int options);

     pid取值:

        ①< -1: 等待进程组id为pid的子进程的结束

      ②   -1: 等待任意子进程的结束(任意一个)

      ③    0: 等待进程组id跟父进程进程组id相同的子进程的结束

      ④  >0:等待进程id为pid的子进程的结束

     

     Options可以是以下几个常数中的一个或多个

      ①WNOHANG: 如果没有子进程退出的话马上返回

      ②WUNTRACED:如果有子进程停止的话返回

      ③WCONTINUED:如果一个停止的子进程重新开始执行的话返回(发送SIGCONT)

    ii 如果父进程很忙,可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。

     signal系统调用详解:

      功能描述:为指定的信号安装新的处理句柄。信号处理句柄可能是用户指定的函 数,SIG_IGN 或 SIG_DFL。当信号到达时,如果其处理句柄是SIG_DFL,那么会以默认的方式处理信号;如果其处理句柄是SIG_IGN,那么信号会被忽略;最 后,如果处理句柄是用户指定的函数,此时先将信号处理方式重置为SIG_DFL,接着有可能阻塞处理中的信号,最后是调用信号处理句柄。

      用法:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

      参数: 
        signum:信号编码。
        handler:新的信号处理句柄。

      返回说明: 
        成功执行时,返回以前的信号处理句柄。失败返回SIG_ERR。

    iii 如果父进程不关心进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,内核会回收,并不再给父进程发送信号。

    iv Stevens的两次fork避免僵尸进程:就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。

  状态标志:

  

  信号:

  

    

  三种方式执行多任务处理:轮询、中断、DMA(与中断的区别)

  Questions

  为什么两次fork可以将孙进程托孤给init进程?

  

  handler句柄是什么东西?

  信号处理句柄可能是用户指定的函 数,SIG_IGN 或 SIG_DFL。

4、进程组

  一个或多个进程的集合

  作业控制

  getpgrp() & setpgid()

  

To be continued...

时间: 2024-10-13 18:41:43

Linux系统编程的相关文章

LINUX系统编程 由REDIS的持久化机制联想到的子进程退出的相关问题

19:22:01 2014-08-27 引言: 以前对wait waitpid 以及exit这几个函数只是大致上了解,但是看REDIS的AOF和RDB 2种持久化时 均要处理子进程运行完成退出和父进程需要做的什么事情,所以特定看了UNIX环境编程和LINUX系统编程这2本书 重新梳理下整个要点. 内容: 一般而言: 如果程序类似于下面的情况: if((pid=fork())==0) { dochildtthing(); exit(0); } else if(pid>0) { dofathertt

Linux系统编程-setitimer函数

功能:linux系统编程中,setitimer是一个经常被使用的函数,可用来实现延时和定时的功能. 头文件:sys/time.h 函数原型: int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 参数含义: 1.which参数用来设置定时器类型,可选的值为 (1)ITIMER_REAL : 设置定时器以系统真实所花费的时间来计时,运行指定时间后发送SIGALRM信号. (

Linux系统编程笔记

写在开篇:出于对未来职业规划的考虑(其实还是一团糟),制定了一个基本的学习方向,那就是从系统编程学习API慢慢的深入内核,这是一个比较成熟的学习路线.所以从本篇开始,在这段时间会陆续记录Linux系统编程的学习笔记,除了供学习之余复习只用,同时也期望能记录初入职场摸爬滚打的第一个3年. 第一章 文件I/O 文件访问的基本调用一般是 read()和write(),但是在访问文件之前,要做的是一项很重要的工作就是:打开,没错!通过调用 open()或create()实现 #include <sys/

Linux系统编程@进程通信(一)

进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统的一个分支) POSIX进程间通信(POSIX:可移植操作系统接口,为了提高UNIX环境下应用程序的可移植性.很多其他系统也支持POSIX标准(如:DEC OpenVMS和Windows).) 现在Linux使用的进程间通信方式包括: 管道(pipe).有名管道(FIFO) 信号(signal) 消

linux系统编程之管道(一):匿名管道(pipe)

原文地址:http://www.cnblogs.com/mickole/p/3192210.html 一,什么是管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道: 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. 数据的读

Linux系统编程之访问文件夹及其文件属性

1. 文件夹操作:opendir, readdir, closedir 2. 文件属性:lstat 3. 实现功能:获取指定文件夹下所有的文件(使用递归),因此就能计算所有文件大小之类的啦... 代码示例如下: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/stat

linux系统编程之信号(四)

今天继续探讨信号相关的东东,话不多说,正入正题: 信号在内核中的表示: 下面用图来进一步描述这种信号从产生到递达之间的状态(信号阻塞与未诀): 那是怎么来决定的呢?下面慢慢来举例分解: 所以,通过这些图,可以描述信号从产生到递达的一个过程,上面的理解起来可能有点难,下面会用代码来进一步阐述,在进行实验之前,还需了解一些函数的使用,这些函数在实验中都会被用到,也就是信号集操作函数. 信号集操作函数: 其中解释一下sigset_t,百度百科解释为: 而这个函数的意义就是将这64位清0 这个函数的意义

Linux系统编程札记:进程通信(一) &nbsp; &nbsp;

进程简单来讲就是一个程序的一次执行,这里说的进程一般都指的是运行在用户态的进程,而处于用户态的不同进程之间是彼此相互隔离的,它们必须通过某种方式来进行通信,具体理由如下: (1)数据传输:有时候一个进程需要将它的数据发送给另一个进程. (2)资源共享:有时候多个进程之间需要共享同样的资源. (3)通知事件:有时候一个进程需要向另一个或一组进程发送消息,通知它们发生了某个事件. (4)进程控制:有些进程希望能够完全控制另一个进程的执行,此时控制进程希望能够拦截另一进程的所有操作,并能够及时知道它的

嵌入式 Linux系统编程(一)——文件IO

嵌入式 Linux系统编程(一)--文件IO 一.文件IO概念 linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作.不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出.它以文件标识符(整型)作为文件唯一性的判断依据.这种操作不是ASCI标准的,与系统有关,移植有一定的问题.而带缓存的是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨OS,成为ASCI标准,称为标准IO库.不带缓存的方式频繁进行用户态 和内核

嵌入式 Linux系统编程(二)——文件描述符控制函数fcntl

嵌入式 Linux系统编程(二)--文件描述符控制函数fcntl 由于fcntl函数实在过于灵活和复杂,本文将fcntl函数从文件IO中单独列出来,便于详细解读.函数原型如下: #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); fcntl函数用于控制操作文件描述符fd,对文件描述符的控制操作由cmd控制命令来控制,arg参数为可选参数,是否需要arg参数取决于控制命令